很少有替代选项(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;
您的 2 个示例之间的主要区别是:
- 示例 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;
这与 Windows API 的工作方式相同。它确实有其优点和缺点:
- 使用返回码意味着调用者必须记住检查错误。 (本网站上 WinAPI 问题的常见来源涉及程序员未能检查 API 函数返回的错误。)
- 因此,调用者不能“忽略”异常模型显然是一个优势 - 它们最终会浮出水面,即使它涉及崩溃应用程序。
- 但反过来又是一个缺点,即忽略强加给您的异常的代码比较混乱。
- 同样重要的是要注意不要陷入除块之外运行大量代码的情况1。
- 结构异常处理的另一个缺点是它确实具有显着的性能开销,因此理想情况下您不希望过于频繁地引发和处理它们。
我建议最好的方法是确定哪些类型的错误可以被视为“正常”,并确保使用明确的错误结果而不是异常来处理这些错误。当然,上述 1 的实例是主要候选对象。
最后,David 已经在示例 2 中标记了您对消息对话框的担忧。因此,此评论是基于此代码始终在用户上下文中运行的假设。
我理解立即显示信息的冲动。您的上下文在异常传播到应用程序级处理程序时丢失。要考虑的一种选择是使用Abort,它只会引发EAbort 异常。
try
//...
except
on E: ExceptionType do
begin
MessageDlg(...);
Abort;
end;
end;
默认应用程序异常应忽略此异常并且不显示消息。如果您有自己的处理程序,则应在显示任何消息之前类似地检查异常类。
作为旁注,我想考虑问题中的一个特定句子。
我读到最好保留应用程序堆栈或队列或类似的东西。
显然,如果您不确定自己阅读的内容,很难向您解释。根据我之前的回答,您可能已经有了更清晰的认识。
但是,它可能指的是使用另一种异常处理方法的不同问题。引发新的异常。 (使用raise; 可以避免这个问题,因为它会在 original 上下文中重新引发 original 异常。)这样做是为了提供“更有意义的错误”消息 - 类似于您的 示例 2。
try
except
raise EOtherError.Create('My Message');
end;
上面的问题是,当这个异常最终传播到应用程序处理程序时,你就失去了原来的类;原始异常地址;和原始消息。这种方法通常会给用户带来更清晰的错误:例如“无法打开文件 filename”,但隐藏了可能对故障排除有用的信息。例如。是磁盘错误,是文件不是文件,是访问权限错误。
所以:无论何时处理错误(无论您使用哪种方法),都需要考虑的重要一点是:如果错误发生,是否有足够的信息来解决错误?