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

Rus*_*sta 2 delphi delphi-10.1-berlin

尝试呼叫退出是否安全?或者我应该拨打加薪

我尝试了下面的两个示例,并在举例中,跟踪通过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
Run Code Online (Sandbox Code Playgroud)

例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
Run Code Online (Sandbox Code Playgroud)

Dis*_*ned 11

很少有替代选项(A与B)可以客观地评估,因为一个选项"总是比另一个更好".这就是为什么正确理解每个的差异和含义是很重要的.

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

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

procedure Caller2;
begin
  //...[C]
  CallDatabaseMethod; {Will raise; or Exit; based on example chosen}
  //...[D]
end;
Run Code Online (Sandbox Code Playgroud)

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

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

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

然而,我怀疑,更多的,往往不是一个事实,CallDatabaseMethod 未能尽一切正确建议,[B]和[d]不应该被调用.例如,假设数据库方法更新客户帐户数据,[B]和[D]执行与发送最新语句相关的操作.您可能不希望在更新失败时发送声明!

也就是说,如果您的方法可以被认为是"成功完成" ,尽管例外,那么通过吞噬异常是完全可以接受的.例如假设您有一个"添加行"的方法,其后置条件只是该行必须存在于数据库中.然后,如果您的数据库返回PK违规,显然该行确实存在.在这种情况下,吞下异常是完全合理的.


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

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

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;
Run Code Online (Sandbox Code Playgroud)

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

  • 使用返回代码意味着调用者必须记住检查错误.(此站点上WinAPI问题的常见来源涉及程序员未能通过API函数检查错误返回.)
  • 因此,调用者不能"忽略"异常模型显然是一个优势 - 它们最终会浮出水面,即使它涉及崩溃应用程序.
  • 但反过来又有一个缺点,即忽略强加给你的异常的代码更加混乱.
  • 同样重要的是要注意避免进入除块1之外的大量代码运行的情况.
  • 结构异常处理的另一个缺点是它确实具有显着的性能开销,因此理想情况下,您不希望过于频繁地引发和处理它们.

我建议最好的方法是确定哪种错误可以被认为是"正常的",并确保使用显式错误结果而不是异常处理.当然,上述1的实例是主要候选人.


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

我理解立即显示消息的冲动.您有异常传播到应用程序级别处理程序时丢失的上下文.要考虑的一个选择是使用Abort简单地引发EAbort异常.

try
  //...
except
  on E: ExceptionType do
  begin
    MessageDlg(...);
    Abort;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

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


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

我读到最好保留应用程序堆栈或队列或类似的东西.

显然,如果你不确定你读的是什么,很难向你解释.根据我的答案的前面部分,您可能已经有了更清晰的图片.

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

try

except
  raise EOtherError.Create('My Message');
end;
Run Code Online (Sandbox Code Playgroud)

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

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


Dav*_*nan 10

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

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

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

因此,行为会有所不同,由您来决定您想要的是什么.

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

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

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