Delphi错误处理:尝试提升vs退出...除...结束

时间:2017-07-23 12:49:43

标签: delphi-10.1-berlin delphi

尝试除<?>退出是否安全? 或者我应该拨打加注

我尝试了下面的两个示例,并在举例中,跟踪经历了Delphi的内部库代码。退出时只退出程序,仅此而已。

我认为最好保留应用程序堆栈或队列或类似的东西。调用exit会打破那个堆栈吗?

示例1(加注)

SDDatabase1.StartTransaction;
Try
  SDQuery1.ApplyUpdates;
  SDDatabase1.Commit;
  SDQuery1.CommitUpdates;
Except
  SDDatabase1.Rollback;
  SDQuery1.RollbackUpdates;
  raise;
End;
..............//other codes I don't want to execute

示例2(退出)

SDDatabase1.StartTransaction;
Try
  SDQuery1.ApplyUpdates;
  SDDatabase1.Commit;
  SDQuery1.CommitUpdates;
Except
  SDDatabase1.Rollback;
  SDQuery1.RollbackUpdates;
  MessageDlg('Save Failed because: '+E.Message, mtError, [mbOK], 0);
  exit;
end;
..............//other codes I don't want to execute

2 个答案:

答案 0 :(得分:11)

很少有替代选项(A与B)可以客观地评估,因为一个人总是更好&#34;比另一个。这就是为什么正确理解每种方法的差异和含义非常重要。

当单独检查单个方法时,您的示例都会在块结束后跳过代码。但是,一个离开异常状态而另一个不离开。这不会影响您编写的方法,而是影响方法的调用者(直接和间接)。

procedur Caller1;
begin
  //...[A]
  Caller2;
  //...[B]
end;

procedure Caller2;
begin
  //...[C]
  CallDatabaseMethod; {Will raise; or Exit; based on example chosen}
  //...[D]
end;

两个例子之间的关键区别是:

  • 示例1能够向调用堆栈报告故障状态。
  • 示例2隐藏了此信息,因为异常处理程序吞下异常。

示例1 也会跳过 [B]和[D]代码。但是,例2将执行[B]和[D]代码。当您了解这种差异时,您就有权决定[B]和[D] 是否 执行。

但是,我怀疑CallDatabaseMethod 无法正确执行所有操作的事实表明[B]和[D]应该不被称呼。例如。假设数据库方法更新客户帐户数据,[B]和[D]执行与发送最新语句相关的操作。 您可能不希望在更新失败时发送声明

那就是说,如果你的方法可以被视为&#34;成功完成&#34; 尽管例外,但通过吞噬异常的方式完全可以接受。例如。假设你有一个方法来&#34;添加一行&#34;它的post条件只是该行必须存在于数据库中。然后,如果您的数据库返回PK违规,显然行确实存在在这种情况下,吞下异常非常有意义。

您当然可以调整示例2的实现,以便隐藏错误

如果您的方法被编写为一个返回成功或失败状态的函数,那么调用者可以使用它来解决上述问题。 E.g。

function Caller1: Boolean;
begin
  Result := Caller2;
  {Caller can decide to skip/ignore/do something different}
  if Result then ...
end;

function Caller2: Boolean;
begin
  Result := CallDatabaseMethod;
  {Caller can decide to skip/ignore/do something different}
  if Result then ...
end;

function CallDatabaseMethod: Boolean;
begin
  Result := True;
  //...
  try
    //...
  except
    on E: ExceptionType do
    begin
      //...
      Result := False;
    end;
  end;
  //...
end;

这与Windows API的工作方式相同。它确实有其优点和缺点:

  • 使用返回代码意味着调用者必须记住检查错误。 (此站点上WinAPI问题的常见来源涉及程序员未能通过API函数检查错误返回。)
  • 因此,明显的优势是不能忽略异常模型&#34;通过呼叫者 - 他们最终会浮出水面,即使它涉及使应用程序崩溃。
  • 但反过来又有一个缺点,就是忽略强加给你的异常的代码更加混乱。
  • 除了阻止 1 之外,还要注意防止大量代码在内部运行。
  • 结构异常处理的另一个缺点是它确实有很大的性能开销,所以理想情况下你不想经常提升和处理它们。

我建议最好的方法是确定可以考虑哪种错误&#34;正常&#34;并确保使用显式错误结果而不是异常处理此问题。当然,以上 1 的实例是主要候选人。

最后David已经在示例2中标记了对您的消息对话框的关注。因此,此注释假设此代码始终在用户上下文中运行。

我理解立即显示消息的冲动。您有异常传播到应用程序级别处理程序时丢失的上下文。要考虑的一个选项是使用Abort,它只会引发EAbort例外。

try
  //...
except
  on E: ExceptionType do
  begin
    MessageDlg(...);
    Abort;
  end;
end;

默认应用程序异常应忽略此异常并且不显示消息。如果你有自己的处理程序,你应该在显示任何消息之前检查异常类。

作为附注,我想考虑问题中的特定句子。

  

我读到保留应用程序堆栈或队列或其他类似的东西更好。

显然,如果您不确定自己所阅读的内容,很难向您解释。根据我的答案的前面部分,您可能已经有了更清晰的图片。

但是,它可能指的是另一种异常处理方法的另一个问题。提出新的例外。 (您可以使用raise;来避免此问题,因为它会在原始上下文中重新引发原始异常。)这样做是为了提供&# 34;更有意义的错误消息 - 类似于示例2

try

except
  raise EOtherError.Create('My Message');
end;

上面的问题是,当这个异常最终传播到应用程序处理程序时,你已经丢失了原来的类;原始异常地址;和原始的消息。这种方法通常会给用户带来更明确的错误:例如&#34;无法打开文件filename&#34;但隐藏可能在故障排除中有用的信息。例如。它是磁盘错误,文件不是文件,是访问权限错误。

因此:无论何时处理错误(无论使用何种方法),都要考虑的重要事项是:是否会有足够的信息来解决错误?

答案 1 :(得分:10)

原则上两者都是安全的,但不可能推荐一种或其他方法。这取决于你的设计意图。鉴于代码的预期用途,您必须决定哪个是合适的。

如果您在此代码中处理异常,并将函数保留为exit,则执行将返回到调用函数,并且它不知道函数是成功还是失败。这可能有问题。

如果你重新引发异常,执行将移动到调用堆栈的下一个合适的异常处理程序,并沿途传递任何finally块。

所以,行为会有所不同,由你来决定你想要的是什么。

尝试在调用堆栈中进一步处理异常是一个常见的初学者错误。例如,假设您希望在GUI应用程序和非可视应用程序中使用您的代码。您对MessageDlg的使用不适合非可视应用。

在GUI应用程序中,大多数操作通常是响应用户输入,例如按下按钮。异常通常会导致整个操作中止。在这种情况下,您根本不应尝试处理异常。让它们传递给应用程序级异常处理程序。

最后,您的代码以相同的方式处理所有异常。这通常是不明智的。例如,访问冲突肯定会与数据库错误区别对待。