将所有内容包装在 try/catch 块中是不是构成防御性编程?

Posted

技术标签:

【中文标题】将所有内容包装在 try/catch 块中是不是构成防御性编程?【英文标题】:Does wrapping everything in try/catch blocks constitute defensive programming?将所有内容包装在 try/catch 块中是否构成防御性编程? 【发布时间】:2008-12-02 15:28:50 【问题描述】:

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

【问题讨论】:

不确定。这个问题虽然标题很笼统,但实际上非常具体地涉及使用异常作为一种防御性编程方法,以及这样做有什么问题。 【参考方案1】:

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

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

【讨论】:

没错。将所有内容都放在 try/catch 语句中通常表明开发人员缺乏经验,对异常 IME 了解不多。 如果我们花费像降级这样的代表点数,我们应该被允许 mod+2。但我会在这里做。 我认为有时,特别是在跨越模块或库的边界时,捕获异常并将其包装在不同类型的异常中(如 Java 的原因链工具)是一种很好的做法,并重新抛出。 这将是“部分处理”,它允许重新抛出,可能会有不同的异常。在我的书中,异常翻译是部分处理。【参考方案2】:

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

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

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

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

【讨论】:

【参考方案3】:

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

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

【讨论】:

无论 errno 是什么,我都会立即想起 printf 打印“不是打字机”,这是否表明了我的年龄?【参考方案4】:

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

private String name;

public void setName(String name) 

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

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

另一个例子:

public boolean isXXX (String s) 

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

或者:

public String getName() 

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

【讨论】:

【参考方案5】:

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

向用户显示友好的消息,并且 保存诊断。

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

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

总的来说,If...Else 比 Try...Catch 好得多。

【讨论】:

我不同意。您可以通过这种方式处理一些异常,但也有一些您需要深入处理的异常——例如,我的一些代码会引发“ScheduleConflictException”,而更高级别的代码可以更改计划并重试。 @Paul,“在大多数情况下,”我认为个人例外是一个例外。在这种情况下,您正在创建一个异常来处理更高层,我不认为这是 cris5gd 考虑的一般情况。 @Stephan,上面写着“将此添加到计划”的代码比评估当前计划是否有效的代码高出很多层,异常是获取该信息的最佳方式贯穿所有层。【参考方案6】:

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

忽略它们?优秀。让我知道这对他们有什么作用。 记录它们并继续运行?我想不是。 作为崩溃的一部分抛出不同的异常?祝你调试顺利。

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

【讨论】:

#3 有什么难调试的? @Bart van Heukelom:掩盖原始异常的不同异常使日志混乱。日志消息是“SomeRandomException”。根本原因是ValueError。此外,原始回溯很容易在引发新异常的混乱中迷失。试试看。 这就是 Java 的原因链的用途。我假设其他语言也有这个(我知道甚至 php 5.3 也有)。 @Bart van Heukelom:Python 有链式异常。这会有所帮助,但前提是有人确实正确地做到了。除非转储 整个 异常(很少见),否则您仍然不知道它是链式异常的一部分。调试异常重写仍然很困难。在别人的代码中试一试。这真的很难。问题不在于链式异常或类似的东西。问题是关于过度或不必要地捕获异常,这是一个不同的主题。【参考方案7】:

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

【讨论】:

完全同意!当然是比喻性的。实际上并不主张在任何人的任何部位开枪。 只写一个方法叫做shootProgrammer(Programmer p) throws OutOfAmmoException @Bill - 那我用卷起的报纸拍他们可以吗? 让他们洗掉所有在休息室水槽里溃烂的咖啡杯怎么样?【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

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

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

【讨论】:

【参考方案11】:

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

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

【讨论】:

【参考方案12】:

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

太多了?旁观者之眼。

我发现将日志复制到 Word 并使用“查找”进行搜索(如果日志阅读器没有将“查找”或“搜索”作为其随附工具的一部分)是一种简单但出色的方法费力地浏览详细的日志。

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

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

【讨论】:

以上是关于将所有内容包装在 try/catch 块中是不是构成防御性编程?的主要内容,如果未能解决你的问题,请参考以下文章

java中的“try - catch -finally”结构中的“finally”都有哪些用途

try catch finally return

在PHP Try Catch块中抛出异常

在 PHP Try Catch 块中抛出异常

try/catch/finally

若catch中抛出运行时异常,请问在try catch语句块中return语句是写在try中还是写在try catch语句外。