在某些情况下使用Abort来改进/简化代码

时间:2017-02-15 06:27:04

标签: delphi abort

前几天我进行了讨论:https://stackoverflow.com/a/42156860/937125 在那里我不太明白为什么Abort比在这种情况下调用Exit更好。我倾向于不在我的代码流中使用它。我认为这是一个不好的做法,对代码流程也不好。 但是@ David在评论中的陈述使我想知道是否可能遗漏了一些东西:

  

如果没有静默异常,你会在深度时如何中止操作   调用堆栈。例如,你将如何中止文件副本   使用10深度调用堆栈进行操作?这不是那个   例外是为...而设计的?当然,你可以编码,没有例外   但它更冗长,更容易出错。

我无法想象这种情况。有人可以给我一个这样的代码/场景的例子,并说服我在上面的案例中Abort确实是一件好事而且“更加冗长和容易出错”。 (3-4深度调用堆栈足以说明)

2 个答案:

答案 0 :(得分:7)

说明我的观点的最简单方案是这样的:

procedure MethodA;
begin
  MethodB;
  MethodC;
end;    

procedure MethodB;
begin
  // ... do stuff
end;

procedure MethodC;
begin
  // ... do stuff
end;

这很好。现在假设MethodB要求用户输入一些内容,如果用户按下取消按钮,则不应再执行任何操作。你可以像这样实现:

procedure MethodA;
begin
  if MethodB then
    MethodC;
end;    

function MethodB: Boolean;
begin
  Result := MessageDlg(...)=mrOK;
  if not Result then
    exit;
  // ... do stuff
end;

procedure MethodC;
begin
  // ... do stuff
end;

这很好用,但想象一下你在现实世界的代码中,有更深的嵌套。 MethodB返回的布尔值可能需要在很多级别上传递。这会变得很麻烦。

或者考虑如果MethodB需要向其调用者返回值,会发生什么。在那种情况下,原始代码可能是这样的:

procedure MethodA;
begin
  MethodC(MethodB);
end;    

function MethodB: string;
begin
  Result := ...;
end;

procedure MethodC(Value: string);
begin
  // ... do stuff with Value
end;

现在再次考虑如果用户有机会取消会发生什么。我们如何从MethodB返回布尔值和字符串?使用out参数作为其中一个返回值?使用像记录这样的复合结构来包装两个值。后者显然涉及许多样板,所以让我们探索前者。

procedure MethodA;
var
  Value: string;
begin
  if MethodB(Value) then
    MethodC(Value);
end;    

function MethodB(out Value: string): Boolean;
begin
  Result := MessageDlg(...)=mrOK;
  if not Result then
    exit;
  Value := ...;
end;

procedure MethodC(Value: string);
begin
  // ... do stuff with Value
end;

肯定你可以这样做,但这开始看起来像是为了简化而设计的例外代码。在这一点上,让我们考虑通过调用EAbort引发的静默异常Abort的存在,这不会导致顶级异常处理程序显示消息。最后一点是 silent 的含义。

现在代码变为:

procedure MethodA;
begin
  MethodC(MethodB);
end;    

function MethodB: string;
begin
  if MessageDlg(...)<>mrOK then
    Abort;
  Result := ...;
end;

procedure MethodC(Value: string);
begin
  // ... do stuff with Value
end;

优点是MethodA无需担心取消。如果调用堆栈更深,则顶部MethodA和用户输入点MethodB之间的方法都不需要了解有关取消的任何内容。

另一个好处是MethodB可以保留其自然特征。它返回string。如果发生故障,无论是来自更传统的异常,还是来自用户取消,都会引发异常。

这个非常简单的示例并不比之前使用Abort的示例更具吸引力。但是想象一下如果MethodB在调用堆栈中是4或5深,代码会是什么样子?

我绝对不是说应该始终使用Abort代替exit。我的信念是两者都有自己的位置。 Abort闪耀的地方是当用户选择取消操作并且您不希望在当前事件处理程序中进行更多处理时。此外,由于用户明确选择取消,因此不需要向他们呈现进一步的UI。你不需要一个消息框告诉用户他们取消了,他们已经知道了。

答案 1 :(得分:4)

假设您的程序在一个单独的线程中执行冗长的操作,或者(即使它不赞成)调用Application.ProcessMessages。现在,您希望用户能够以安全的方式中止该操作(即:清除所有资源,数据处于一致状态等)。因此,UI在某处设置了一个标志,并在您的代码中定期检查该标志。如果已设置,则调用Abort或显式提升EAbort。这将导致您执行所有精心设计的try / except / finally块,并确保中止操作是安全的。

// in the main thread:
procedure TMyProgressDialog.b_AbortClick(Sender: TObject);
begin
  if AskUserIfHeIsSure then begin
    gblAbortedFlag := true;
    b_Abort.Enabled := false;
    b_Abort.Caption := _('Aborting');
  end;
end;

// call this repeatedly during the lenghty operation:
procecdure CheckAborted;
begin
  // If you are in the main thread, you might want to call
  // Application.ProcessMessages;
  // here. If not, definitely don't.
  if gblAbortedFlag then
    Abort;
end;

当然这可以通过一个不同的异常来完成,但是我无法想到任何其他方式来安全地退出深度调用堆栈而无需编写大量的ifs和exit。