33

在过去的 3 年里,我一直在编程。当我编程时,我习惯于处理所有已知的异常并优雅地提醒用户。我最近看到了一些代码,其中几乎所有方法都包含在 try/catch 块中。作者说它是防御性编程的一部分。我想知道,这真的是防御性编程吗?您是否建议将所有代码放在 try 块中?

4

12 回答 12

62

我的基本规则是:除非你能解决导致异常的问题,否则不要抓住它,让它冒泡到可以处理的水平。

根据我的经验,95% 的 catch 块要么只是忽略异常 ( catch {}),要么只是记录错误并重新抛出异常。后者可能看起来是正确的做法,但实际上,当在每个级别都这样做时,您最终只会让您的日志杂乱无章地出现五个相同错误消息的副本。通常这些应用程序在最顶层有一个“忽略捕获”(因为“我们在所有较低级别都有尝试/捕获”),导致应用程序非常慢,有很多错过的异常,以及一个太长的错误日志任何人都愿意看透它。

于 2008-12-02T15:31:47.770 回答
22

Try...Catch 的广泛使用不是防御性编程,它只是将尸体钉在一个直立的位置

Try...Final 可广泛用于面对意外异常时的恢复。只有当您期望出现异常并且现在如何处理它时,您才应该使用 Try..Catch 代替。

有时我会看到 Try..Catch System.Exception,其中 catch 块只记录异常并重新抛出。这种方法至少存在 3 个问题:

  • Rethrow 假定一个未处理的异常,因此程序应该终止,因为它处于未知状态。但是 catch 会导致 Catch 块下面的 finally 块运行。在未定义的情况下,这些 finally 块中的代码可能会使问题变得更糟。
  • 那些 finally 块中的代码将改变程序状态。因此,最初抛出异常时,任何日志记录都不会捕获实际的程序状态。而且由于状态发生了变化,调查会更加困难。
  • 它给您带来了痛苦的调试体验,因为调试器在重新抛出时停止,而不是在原始抛出时停止。
于 2008-12-02T16:55:14.870 回答
14

不,这不是“防御性编程”。你的同事正试图通过使用流行词来为他的坏习惯合理化一个好习惯。

他的所作所为应该被称为“扫地出门”。这就像统一地(void)从方法调用中获取错误状态返回值。

于 2008-12-02T15:50:12.070 回答
11

术语“防御性编程”代表以这样一种方式编写代码,即它可以从错误情况中恢复或完全避免错误情况。例如:

private String name;

public void setName(String name) {
}

你如何处理name == null?你抛出异常还是接受它?如果没有名称的对象没有意义,那么您应该抛出异常。那么名称==“”呢?

但是……以后你写一个编辑器。在设置 UI 时,您会发现在某些情况下,用户可以决定取消名称,或者在用户编辑名称时名称可能变为空。

另一个例子:

public boolean isXXX (String s) {
}

在这里,防御策略通常是在 s == null 时返回 false(尽可能避免 NPE)。

或者:

public String getName() {
}

如果 name == null ,防御性程序员可能会返回 "" 以避免调用代码时出现 NPE。

于 2008-12-02T15:39:40.327 回答
9

如果您要处理随机异常,请仅在一个地方处理它们 - 应用程序的最顶部,目的是:

  • 向用户呈现友好的消息,以及
  • 保存诊断。

对于其他所有事情,您都希望尽可能立即发生特定于位置的崩溃,以便尽早捕获这些事情 - 否则异常处理将成为隐藏草率设计和代码的一种方式。

在异常是可预测的大多数情况下,可以提前测试异常处理程序将捕获的条件。

一般来说,If...Else 比 Try...Catch 要好得多。

于 2008-12-02T15:45:26.853 回答
8

捕获随机异常是不好的。然后怎样呢?

  • 别理他们?出色的。让我知道这对他们有什么作用。
  • 记录它们并继续运行?我想不是。
  • 作为崩溃的一部分抛出不同的异常?祝你调试好运。

捕获您实际上可以做一些有意义的事情的异常是很好的。这些案例很容易识别和维护。

于 2008-12-02T15:34:07.857 回答
6

我可以在这里顺便说一句,每次我的一个同事写一个带有“抛出异常”的方法签名而不是列出该方法真正抛出的异常类型时,我想过去并把它们射在头上?问题是,过了一段时间,你得到了 14 个级别的调用,所有这些调用都说“抛出异常”,因此重构以使它们声明它们真正抛出的内容是一项重大练习。

于 2008-12-02T15:41:19.560 回答
5

有“太多”处理这样的事情,并且捕获所有异常有点失败。特别是对于 C++,catch(...) 语句确实捕获了所有异常,但您无法处理该异常的内容,因为您不知道异常的类型(它可能是任何东西)。

您应该捕获可以完全或部分处理的异常,重新抛出部分异常。您不应该捕获任何您无法处理的异常,因为这只会混淆以后可能(或者更确切地说,将会)咬到您的错误。

于 2008-12-02T15:32:01.480 回答
4

我建议反对这种做法。当您知道可以抛出的异常类型时,将代码放入 try-catch 块中是一回事。正如您所说,它允许您优雅地恢复和/或提醒用户注意错误。但是,将所有代码放在您不知道可能发生什么错误的块中是使用异常来控制程序流,这是一个很大的禁忌。

如果您正在编写结构良好的代码,您将了解可能发生的每个异常,并且可以专门捕获这些异常。如果您不知道如何抛出特定异常,请不要捕获它,以防万一。当它发生时,您可以找出异常、导致它的原因,然后捕获它。

于 2008-12-02T15:33:39.263 回答
3

在 C++ 中,编写大量 try/catch 块的一个原因是为了获取引发异常的位置的堆栈跟踪。你所做的是到处写一个 try/catch,并且(假设你不在正确的位置来处理异常)让 catch 记录一些跟踪信息,然后重新抛出异常。这样,如果一个异常一直冒泡并导致程序终止,您将获得所有开始出错的完整堆栈跟踪(如果您不这样做,那么未处理的 C++ 异常将有助于解开堆栈并消除您弄清楚它来自哪里的任何可能性)。

我想在任何具有更好异常处理的语言中(即未捕获的异常会告诉您它们来自哪里),您只想捕获异常,如果您可以对它们做点什么。否则,你只是让你的程序难以阅读。

于 2008-12-02T20:29:16.763 回答
3

我想真正的答案是“这取决于”。如果 try-catch 块捕获非常通用的异常,那么我会说它是防御性编程,就像从不开车离开你的社区就是防御性驾驶一样。try-catch (imo) 应该针对特定的异常进行定制。

同样,这只是我的观点,但我对防御性编程的概念是你需要更少/更小的 try-catch 块而不是更多/更大的块。您的代码应该尽其所能确保从一开始就永远不会存在异常条件。

于 2008-12-02T15:32:45.350 回答
2

我发现“try”“catch”块非常有用,尤其是在使用任何实时(例如访问数据库)时。

太多?旁观者之眼。

我发现将日志复制到 Word 并使用“find”进行搜索——如果日志阅读器没有将“find”或“search”作为其包含的工具的一部分——是一种简单但极好的方式来通过详细的日志。

从这个词的普通意义上来说,它当然看起来是“防御性的”。

根据经验,我发现无论你的经理、团队领导或同事做什么,我都可以追随。如果您只是为自己编程,请使用它们直到代码“稳定”或在调试版本中,然后在完成后将其删除。

于 2008-12-02T20:07:33.873 回答