如何在没有大量 IF 的情况下控制执行?
How to control execution without lots of IFs?
我有一个从文件导入数据开始,然后执行一系列过程的过程,但在任何时候它都能发现问题,应该停止执行其余部分和 运行 另一组。
这是我的示例,其中每个过程都设置了指示停止进程的全局 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;
此工作流程似乎设计不正确。我想知道导入后 运行ning 的主要任务是什么,而不是每次我需要查看它时都进行发现之旅。所有任务都已按目的、它们做什么、如何做以及结果分组到程序中。
结论:
即使不是最好的选择,我还是决定在需要停止进程时使用 Raising Exception。我 'kind of' 理解这种方法带来的影响(一旦我实施它就会知道更多),但这似乎是朝着有朝一日更好地实施整个过程的合乎逻辑的一步。现在我不会为每个任务执行都看到所有这些 IF。好的!代码将更具可读性、可维护性。
我阅读了提供的链接以解释在停止工作流执行时使用异常的陷阱,但是,正如 Dalija Prasnikar 在评论中解释的那样,这不是任务的性能相关部分;每个应用程序只执行一次进程运行;任务已经根据他们所做的事情进行了结构化;任务已经包含多个 IF 语句,其中检查停止的进程等等,所以我认为异常不会成为我问题的一个非常糟糕的解决方案。
此外,如果我将任务转换为返回结果的函数,我想我也会遇到同样的问题,检查每个任务的值并根据该值停止或继续该过程。
所以,引发异常是我的选择。
您应该使用自定义异常并在遇到原因中断工作并转到 Stopped
代码时引发它。在您的 Import
、AfterImport1
和其他代码逻辑中,只需调用 Stop
过程,它将执行 Stopped
过程。另一方面,如果一切顺利,Stopped
将不会被调用。
您可以从 EAbort 创建静默异常
派生异常
type
EStopException = class(EAbort);
或从基础 Exception class 派生以使用常规类型异常。
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;
最好的方法是通过 RTTI
以下是您问题的虚拟实现:
单位 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 函数,它将自动被调用。
类似于基于 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 框架)缺乏任何异常控制,所以如果任何导入处理器会引发一些未捕获的异常 - 执行将跳出主进程循环而不调用清理例程。因此,这意味着您仍然必须在每个参与者(脆弱的)或 Run
过程中捕获可能的异常。但在后一种情况下,您可能会完全省略 gStop
变量,而是引发您的自定义异常。就个人而言,我更喜欢基于异常的方式而不是布尔标志。甚至 EurekaLog 也可能有用,如果您失败的 afterimport
过程会向异常添加一些有意义的消息,说明为什么导入被中止。
PPS。我还将 gStop 分成两个不同的 variables/exceptions:Batch Cancel 和 Import Abort。然后 If fTypeOfData = cMSSQL Then
- 或任何其他先决条件 - 检查可能只是批处理中的第一个演员。然后这些批次可以合并到第二层 array/collection.
我还认为 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 库中的多播事件。
为什么不把程序分成更小的子程序呢?例如:
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;
可读性更高,更易于维护。
我有一个从文件导入数据开始,然后执行一系列过程的过程,但在任何时候它都能发现问题,应该停止执行其余部分和 运行 另一组。
这是我的示例,其中每个过程都设置了指示停止进程的全局 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;
此工作流程似乎设计不正确。我想知道导入后 运行ning 的主要任务是什么,而不是每次我需要查看它时都进行发现之旅。所有任务都已按目的、它们做什么、如何做以及结果分组到程序中。
结论:
即使不是最好的选择,我还是决定在需要停止进程时使用 Raising Exception。我 'kind of' 理解这种方法带来的影响(一旦我实施它就会知道更多),但这似乎是朝着有朝一日更好地实施整个过程的合乎逻辑的一步。现在我不会为每个任务执行都看到所有这些 IF。好的!代码将更具可读性、可维护性。
我阅读了提供的链接以解释在停止工作流执行时使用异常的陷阱,但是,正如 Dalija Prasnikar 在评论中解释的那样,这不是任务的性能相关部分;每个应用程序只执行一次进程运行;任务已经根据他们所做的事情进行了结构化;任务已经包含多个 IF 语句,其中检查停止的进程等等,所以我认为异常不会成为我问题的一个非常糟糕的解决方案。
此外,如果我将任务转换为返回结果的函数,我想我也会遇到同样的问题,检查每个任务的值并根据该值停止或继续该过程。
所以,引发异常是我的选择。
您应该使用自定义异常并在遇到原因中断工作并转到 Stopped
代码时引发它。在您的 Import
、AfterImport1
和其他代码逻辑中,只需调用 Stop
过程,它将执行 Stopped
过程。另一方面,如果一切顺利,Stopped
将不会被调用。
您可以从 EAbort 创建静默异常
派生异常type
EStopException = class(EAbort);
或从基础 Exception class 派生以使用常规类型异常。
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;
最好的方法是通过 RTTI
以下是您问题的虚拟实现: 单位 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 函数,它将自动被调用。
类似于基于 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 框架)缺乏任何异常控制,所以如果任何导入处理器会引发一些未捕获的异常 - 执行将跳出主进程循环而不调用清理例程。因此,这意味着您仍然必须在每个参与者(脆弱的)或 Run
过程中捕获可能的异常。但在后一种情况下,您可能会完全省略 gStop
变量,而是引发您的自定义异常。就个人而言,我更喜欢基于异常的方式而不是布尔标志。甚至 EurekaLog 也可能有用,如果您失败的 afterimport
过程会向异常添加一些有意义的消息,说明为什么导入被中止。
PPS。我还将 gStop 分成两个不同的 variables/exceptions:Batch Cancel 和 Import Abort。然后 If fTypeOfData = cMSSQL Then
- 或任何其他先决条件 - 检查可能只是批处理中的第一个演员。然后这些批次可以合并到第二层 array/collection.
我还认为 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 库中的多播事件。
为什么不把程序分成更小的子程序呢?例如:
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;
可读性更高,更易于维护。