我有一个流程,从文件导入数据开始,然后执行一系列过程,但是在任何时候它都可以找到问题,应该停止执行其余的并运行另一个集合。
这是我的示例,其中每个过程都设置了指示停止进程的全局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语句,其中检查停止的进程等等,所以我认为异常不会成为我的问题的一个非常糟糕的解决方案。
另外,如果我将任务转换为返回结果的函数,我想我会遇到同样的问题,检查每个任务的值并根据它停止或继续该过程。
因此,我选择提高异常。
答案 0 :(得分:7)
您应该使用自定义异常,并在遇到打破工作原因并转到Stopped
代码时提出异常。在Import
,AfterImport1
和其他代码逻辑中,只需调用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;
更易读,更容易维护。