如何在没有大量IF的情况下控制执行?

时间:2015-12-06 14:12:37

标签: delphi delphi-xe7

我有一个流程,从文件导入数据开始,然后执行一系列过程,但是在任何时候它都可以找到问题,应该停止执行其余的并运行另一个集合。

这是我的示例,其中每个过程都设置了指示停止进程的全局gStop变量。如果它被停止,我需要在最后运行一些代码。

var gStop:boolean;


procedure Run;
begin
  gStop:=False;
  Import; // imports data from file
  If Not gStop Then
    AfterImport1;
  If Not gStop Then
    AfterImport2;
  If Not gStop Then
    AfterImport3;
  If Not gStop Then
    AfterImport4;
  If Not gStop Then
  If fTypeOfData = cMSSQL Then // function returns type of imported data
  begin
    ProcessMSSQLData1;
    If not gStop Then
      ProcessMSSQLData2;
    If not gStop Then
      ProcessMSSQLData3;
    If not gStop Then
      If fObjectAFoundInData Then // function checks if ObjectA was found in imported data
        ProcessObjectA;
    If not gStop Then
      ProcessMSSQLData4;
  end;
  If Not gStop Then
    AfterImport5;
  ...
  // If stopped at anytime
  If gStop then
  begin    
    LogStoppedProcess; 
    ClearImportedData;
    ...  
  end;
end;

在我的情况下,它实际上超过了200行代码,所以当我维护这部分代码时,我必须向上和向下滚动。

我可以通过任何方式改进此程序,使其更具可读性,更易于维护,或者是否有其他方法可以在没有所有IF的情况下停止该过程?

编辑1:

每个过程都可以找到错误的数据,并且可以设置gStop := True;,并且该过程应该跳过所有剩余的过程,并在gStop = True;

时执行最后一部分代码

编辑2:

我想保持工作流程从主程序(运行)控制,这样我就可以看到主要导入后运行的所有任务。如果我将执行分解为许多较小的过程,我只会看到更多的混淆,更少的可读性和可维护性。然后我就可以了:

procedure Run;
begin
  gStop:=False;
  Import; // imports data from file
  RunEverytingAferImport; // execute ALL tasks after import
  // If stopped at anytime
  If gStop then
  begin    
    LogStoppedProcess; 
    ClearImportedData;
    ...  
  end;
end;

此工作流程似乎没有正确设计。我想知道在导入后运行的主要任务是什么,而不是每次我需要查看它时都不进行发现之旅。所有任务都已经按目的分组到程序中,他们做了什么以及如何做,以及结果。

结论:

即使不是最好的选择,我还是决定在需要停止进程时使用Raising Exception。我有点'理解这种方法带来的影响(一旦我实现它就会知道更多),但它似乎是朝着更好地实现整个过程的一个合乎逻辑的步骤。现在我不会为每个任务执行都看到所有这些IF。好!代码将更易读,更易于维护。

我阅读了提供的链接,以解释在停止工作流程执行中使用例外的缺陷,但正如Dalija Prasnikar在评论中所解释的那样,这不是与任务相关的性能部分;每个应用程序运行只执行一次进程;任务已经完成了他们的工作;任务已经包含多个IF语句,其中检查停止的进程等等,所以我认为异常不会成为我的问题的一个非常糟糕的解决方案。

另外,如果我将任务转换为返回结果的函数,我想我会遇到同样的问题,检查每个任务的值并根据它停止或继续该过程。

因此,我选择提高异常。

4 个答案:

答案 0 :(得分:7)

您应该使用自定义异常,并在遇到打破工作原因并转到Stopped代码时提出异常。在ImportAfterImport1和其他代码逻辑中,只需调用Stop过程,它将执行Stopped过程。另一方面,如果一切顺利,将不会调用Stopped

您可以从EAbort创建无提示异常

派生您的例外
type
  EStopException = class(EAbort);

或从base Exception类派生以使用常规类型异常。

type
  EStopException = class(Exception);

procedure Stop(const Msg: string);
begin
  raise EStopException.Create(Msg);
end;

procedure Import;
var sl: TStringList;
begin
  sl := TStringList.Create;
  try
    // your code logic
    if NeedToStop then Stop('something happened');        
  finally
    // perform any cleanup code needed here
    sl.Free;
  end;
end;

procedure Stopped;
begin
end;

procedure Run;
begin
  try
    Import;
    AfterImport1;
    AfterImport2;
  except
    on e: EStopException do
      Stopped;
  end;
end;

答案 1 :(得分:3)

类似于基于RTTI的Jens Borrisholt示例,但没有RTTI。 因此不会绑定到包含所有方法的单个超级对象。

type TAfterImportActor = reference to procedure (var data: TImportData; var StopProcess: boolean);
     TAfterImportBatch = TList< TAfterImportActor >;

var Batch1, Batch2, BatchMSSQL: TAfterImportBatch; // don't forget to create and free them.

procedure InitImportBatches;
begin
  Batch1 := TAfterImportBatch.Create;
  Batch2 := TAfterImportBatch.Create; 
  BatchMSSQL := TAfterImportBatch.Create;

  Batch1.Add( AfterImport1 );
  Batch1.Add( SomeObject.AfterImport2 ); // not only global procedures
  Batch1.Add( SomeAnotherObject.AfterImport3 ); // might be in different modules
  Batch1.Add( AfterImport4 );

  Batch2.Add( AfterImport5 );
...
  Batch2.Add( AfterImport123 );

  BatchMSSQL.Add( ProcessMSSQLData1 );
...
  BatchMSSQL.Add( ProcessMSSQLData5 );
end;

procedure ProcessBatch(const Batch: TAfterImportBatch; var data: TImportData; var StopProcess: Boolean);
var action: TAfterImportActor;
begin
  if StopProcess then exit;

  for action in Batch do begin
    action( data, StopProcess );
    if StopProcess then break;
  end; 
end;

procedure Run;
var gStop: boolean;
    data: TImportData;
begin
  gStop:=False;
  Import(data, gStop); // imports data from file

  ProcessBatch( Batch1, data, gStop );

  If fTypeOfData = cMSSQL Then // function returns type of imported data
     ProcessBatch( BatchMSSQL, data, gStop );

  ProcessBatch( Batch2, data, gStop );
  ...

  // If stopped at anytime
  If gStop then
  begin    
    LogStoppedProcess; 
    ClearImportedData;
    ...  
  end;
end;

PS。这个框架(以及上面的RTTI框架)缺少任何异常控制,因此如果任何导入处理器会引发一些未捕获的异常 - 执行将跳出主进程循环而不调用清理例程。这意味着您仍然必须在每个actor(脆弱)或Run过程中捕获可能的异常。但在后一种情况下,您可能会完全省略gStop变量,而是提高自定义异常。我个人更喜欢基于异常的布尔标志方式。甚至EurekaLog也可能有用,如果失败的afterimport过程会在异常中添加一些有意义的消息,说明为什么导入被中止。

PPS。我还将gStop分成两个不同的变量/例外:批量取消和导入中止。然后If fTypeOfData = cMSSQL Then - 或任何其他先决条件 - 检查可能只是批次中的第一个参与者。然后批次可以组合成第二层阵列/集合。

我还认为EurekaLog会忽略您的自定义异常,如果您从EAbort继承它们 - http://docwiki.embarcadero.com/RADStudio/XE8/en/Silent_Exceptions

type TAfterImportActor = reference to procedure (var data: TImportData; var CancelBatch, AbortImport: boolean);
     TAfterImportBatch = TList< TAfterImportActor >;

var Batch1, Batch2, BatchMSSQL: TAfterImportBatch; 
// don't forget to create and free them.

    ImportBatches: TArray<TAfterImportBatch>;

procedure MSSQLCheck(var data: TImportData; var CancelBatch, AbortImport: boolean);
begin
  CancelBatch := data.fTypeOfData <> cMSSQL; 
end;

procedure InitImportBatches;
begin
  Batch1 := TAfterImportBatch.Create;
  Batch2 := TAfterImportBatch.Create; 
  BatchMSSQL := TAfterImportBatch.Create;

  Batch1.Add( AfterImport1 );
  Batch1.Add( SomeObject.AfterImport2 ); // not only global procedures
  Batch1.Add( SomeAnotherObject.AfterImport3 ); // might be in different modules
  Batch1.Add( AfterImport4 );

  Batch2.Add( AfterImport5 );
...
  Batch2.Add( AfterImport123 );

  BatchMSSQL.Add( MSSQLCheck ); // If fTypeOfData = cMSSQL Then Run This Batch
  BatchMSSQL.Add( ProcessMSSQLData1 );
...
  BatchMSSQL.Add( ProcessMSSQLData5 );

  ImportBatches := TArray<TAfterImportBatch>.Create
     ( Batch1, BatchMSSQL, Batch2);
end;

procedure ProcessBatch(const Batch: TAfterImportBatch; var data: TImportData; var StopProcess: Boolean);
var action: TAfterImportActor; CancelBatch: boolean;
begin
  if StopProcess then exit;

  CancelBatch := false;
  for action in Batch do begin
    action( data, CancelBatch, StopProcess );
    if StopProcess or CancelBatch then break;
  end; 
end;

procedure Run;
var gStop: boolean;
    data: TImportData;
    CurrentBatch: TAfterImportBatch;
begin
  gStop := False;
  Import(data, gStop); // imports data from file

  for CurrentBatch in ImportBatches do begin   
    if gStop then break; 
    ProcessBatch( CurrentBatch, data, gStop );
  end;

  ...

  // If stopped at anytime
  If gStop then
  begin    
    LogStoppedProcess; 
    ClearImportedData;
    ...  
  end;
end;

PPPS。您可能还想查看http://www.uweraabe.de/Blog/2010/08/16/the-visitor-pattern-part-1/

关注不同行动者的注册和调用方式。 这可能会给你一些想法,虽然这不是你的问题。

另一件需要考虑的事情可能是像Spring4D库中那样的多播事件。

答案 2 :(得分:0)

最好的方法是通过RTTI

以下是您问题的虚拟实现:     unit ImportU;

interface

{$M+}

uses
  RTTI;

Type
  TImporter = class
  strict private
    RttiContext: TRttiContext;
    gStop: Boolean;
    function GetMethod(const aMethodName: string): TRttiMethod;
    procedure Import;
  public    
    procedure AfterImport1;
    procedure AfterImport2;
    procedure AfterImport3;
    procedure AfterImport4;
    procedure Run;
  end;

implementation

uses
  Sysutils;
{ TImporter }

procedure TImporter.AfterImport1;
begin

end;

procedure TImporter.AfterImport2;
begin

end;

procedure TImporter.AfterImport3;
begin
  gStop := True;
end;

procedure TImporter.AfterImport4;
begin

end;

function TImporter.GetMethod(const aMethodName: string): TRttiMethod;
begin
  Result := RttiContext.GetType(Self.ClassType).GetMethod(aMethodName);
end;

procedure TImporter.Import;
begin

end;

procedure TImporter.Run;
var
  i: Integer;
  Stop: Boolean;
  RttiMethod: TRttiMethod;
begin
  i := 0;
  repeat
    inc(i);
    RttiMethod := GetMethod('AfterImport' + IntToStr(i));

    if RttiMethod = nil then
      break; //Break loop

    RttiMethod.Invoke(self, []);
  until (gStop = false);
end;

end.

此实现的优点是,如果您创建AfterImport函数,它将自动被调用。

答案 3 :(得分:0)

为什么不将程序划分为较小的子程序?例如:

var gStop:boolean;

procedure AfterImport;
begin
  If Not gStop Then
    AfterImport1;
  If Not gStop Then
    AfterImport2;
  If Not gStop Then
    AfterImport3;
  If Not gStop Then
    AfterImport4;

end;

procedure ProcessMSSQLData;
begin
  If Not gStop Then
  If fTypeOfData = cMSSQL Then // function returns type of imported data
  begin
    ProcessMSSQLData1;
    If not gStop Then
      ProcessMSSQLData2;
    If not gStop Then
      ProcessMSSQLData3;
    If not gStop Then
      If fObjectAFoundInData Then // function checks if ObjectA was found in imported data
        ProcessObjectA;
    If not gStop Then
      ProcessMSSQLData4;
  end;
end;

procedure AfterProcessMSSQLData;
begin
  If Not gStop Then
    AfterImport5;  
end;

通过这种方式,您的最终Run;将有15行代码:

procedure Run;
begin
  gStop:=False;
  Import; // imports data from file
  AfterImport;
  ProcessMSSQLData;
  AfterProcessMSSQLData;
  // If stopped at anytime
  If gStop then
  begin    
    LogStoppedProcess; 
    ClearImportedData;
    ...  
  end;
end;

更易读,更容易维护。