捕获通用异常真的那么糟糕吗?

77

在使用 FXCop 分析一些遗留代码时,我突然想到,在 try 块中捕获通用的异常错误真的那么糟糕吗?还是应该寻找特定的异常类型呢?请您来分享一下您的想法。


2
如果输入不可预测,而失败不是一个选项 - 这是唯一的方法。当然,没有什么好理由不记录错误,以便下次可以正确处理。 - krethika
1
这个问题是否更适合在programmers.stackexchange上提问? - HardcoreBro
我喜欢这也被称为“神奇宝贝捕捉- 必须抓住它们”。 - Uwe Keim
16个回答

118

显然,这是那种唯一真正答案是“取决于情况”的问题之一。

它主要取决于你捕获异常的位置。通常来说,库应该更加保守地捕获异常,而在程序的顶层(例如在主方法中或者控制器的操作方法的顶部等),你可以更加自由地捕获异常。

原因是,例如,你不希望在库中捕获所有异常,因为你可能会掩盖与你的库无关的问题,比如"OutOfMemoryException",你真的希望这些问题能够上浮以便用户得到通知。另一方面,如果你是在main()方法中捕获异常并将其显示然后退出... 那么,在这里捕获几乎任何异常都是安全的。

关于捕获所有异常的最重要的规则是,你永远不应该悄悄地吞噬所有异常... 例如在Java中这样做:

try { 
    something(); 
} catch (Exception ex) {}

或者在Python中这样写:

try:
    something()
except:
    pass

因为这些问题可能是最难追踪的。

一个好的经验法则是,你只应该捕获那些你自己能够正确处理的异常。如果你不能完全处理异常,那么你应该让它冒泡到能够处理的人手中。


11
在语言边界捕获所有异常并进行翻译也是一个好的实践。 - Alexandre C.
这是因为例如在库中你不想捕获所有的异常,因为你可能会掩盖与你的库无关的问题,比如"OutOfMemoryException"。有趣的是,.NET框架有时确实会捕获OutOfMemoryException - jrh

33

除非您在应用程序的前端进行一些日志记录和清理代码,否则我认为捕获所有异常是不好的。

我的基本经验法则是捕获您预期的所有异常,其他任何异常都是错误。

如果你捕获了所有异常并继续执行,就像在汽车仪表板上贴一个黏性带盖住警告灯一样。虽然您看不见灯了,但这并不意味着一切都没问题。


15

是的!(除非在您的应用程序的“顶部”)

通过捕获异常并允许代码继续执行,您表明您知道如何处理和规避或修复特定问题。您声明这是可恢复的情况。捕获 Exception 或 SystemException 意味着您将捕获诸如 IO 错误、网络错误、内存不足错误、缺少代码错误、空指针取消引用等问题。说您能够处理这些问题是虚假的。

在良好组织的应用程序中,应该高层处理这些不可恢复的问题。

此外,随着代码的发展,您不希望您的函数捕获调用方法中未来添加的新异常。


1
catch 可以用于添加额外信息,然后重新抛出,对吗? - Maarten Bodewes

12

我的观点是,你应该捕获所有你预期的异常,但这个规则仅适用于除了接口逻辑以外的所有内容。在整个调用堆栈中,你可能需要创建一种方式来捕获所有异常,进行一些记录/向用户反馈,并且如果需要和可能的话,优雅地关闭。

没有比应用程序崩溃并在屏幕上抛出一些对用户不友好的堆栈跟踪更糟糕的了。它不仅会提供(也许不想要的)代码洞察力,而且还会使最终用户感到困惑,有时甚至会吓跑他们去竞争对手的应用程序。


8

关于这个问题,有很多哲学性的讨论(更像是争吵)。就我个人而言,我认为最糟糕的事情是吞掉异常。其次最糟糕的是允许异常冒泡到用户面前,给用户展示一大堆让人摸不着头脑的技术术语。


10
让异常冒泡并不意味着向最终用户显示它。您可以(并且应该)显示通用错误页面,记录它以进行调查,然后修复首次出现异常的问题。 - jammycakes
2
未回答所问问题。 - Kenneth K.

5

嗯,我不认为捕获一般异常和特定异常之间有任何区别,除非在有多个catch块时,您可以根据异常的类型做出不同的反应。

总之,使用通用的Exception会捕获IOExceptionNullPointerException,但您的程序应该以不同的方式做出反应。


3

我认为这一点有两个方面的含义。

首先,如果您不知道发生了什么异常,那么您如何希望从中恢复过来呢?如果您预期用户可能会输入错误的文件名,那么您可以预计会出现FileNotFoundException并告诉用户再试一次。但是,如果同一段代码生成了NullReferenceException,并且您只告诉用户再试一次,他们就不知道发生了什么。

其次,FxCop准则确实专注于库/框架代码-它们的所有规则都不适用于exe或ASP.Net网站。因此,拥有一个全局异常处理程序,可以记录所有异常并优雅地退出应用程序,这是一个好方法。


编写逻辑时使用异常是不好的,如果您预期用户提供的文件可能不存在,则有一种方法可以检查:File.exists()。 - Krzysztof Cichocki
1
@KrzysztofCichocki - 我知道这已经过时了,但实际上,使用异常来编写文件逻辑的方式更加正确。Eric Lippert在这里有一个很好的解释。他将此类型称为“外生”异常。 - Olly
+1 给 @Olly 的答案:你无法事先检查文件是否存在。总会有竞争条件,在你访问它之前它可能已经被删除了。 - Jonas Benz

3
捕获所有异常的问题在于可能会捕获您不希望或者甚至不应该捕获的异常。事实上,任何类型的异常都表示出现了问题,您必须在继续之前解决它,否则可能会导致数据完整性问题和其他难以跟踪的错误。
举个例子,在一个项目中,我实现了一个名为CriticalException的异常类型。这表示需要开发人员和/或管理人员干预的错误条件,否则客户可能会被错误地计费,或者可能会导致其他数据完整性问题。在其他类似情况下,当仅记录异常不足以解决问题时,也可以使用它,并需要发送电子邮件警报。
然后,另一个开发人员没有正确理解异常的概念,将一些可能引发此异常的代码包装在通用的try...catch块中,从而丢弃了所有异常。幸运的是,我发现了这个问题,但它可能会导致严重的问题,特别是因为“非常罕见”的边缘情况实际上比我预期的要普遍得多。
因此,总的来说,除非您100%确定知道将抛出哪些异常以及在哪些情况下抛出,否则捕获通用异常是不好的。如果有疑问,请将它们上升到顶级异常处理程序。
类似的规则是永远不要抛出System.Exception类型的异常。您(或其他开发人员)可能希望在调用堆栈中更高的位置捕获您的特定异常,同时让其他异常通过。
(然而,有一点需要注意。在.NET 2.0中,如果线程遇到任何未捕获的异常,它将卸载整个应用程序域。因此,您应该在通用的try...catch块中包装线程的主体,并将其中捕获的任何异常传递给全局异常处理代码。)

如果调用“CriticalException”的代码也调用了“Thread.Abort(Thread.CurrentThread)”,会发生什么情况?因为被中止的线程处于已知状态(调用Abort),该方法的正常风险不适用;执行catch(Exception ex)的代码将捕获导致线程中止的异常,但不会阻止其向上调用链传播。当然,最好有一种更有意义的命名异常可以同样工作,但我并不知道.NET中是否有这样的特性。 - supercat

2

我想就捕获异常、记录并重新抛出它这件事提出反对意见。如果在代码中发生了意外的异常,你可以捕获它,并记录简单堆栈跟踪无法提供的有意义的状态信息,然后将其重新抛到上层进行处理,这时就会变得必要。


2
有两种完全不同的用例。第一个是大多数人想到的,就是在需要处理已检查异常的操作周围放置try/catch块。但这绝不能成为万能解决方案。
然而,第二种情况是当程序可以继续执行时防止其崩溃的情况。这些情况包括:
- 所有线程的顶部(默认情况下,异常会悄无声息地消失!) - 在你期望永远不会退出的主处理循环内部 - 在处理对象列表的循环内部,其中一个失败不应该停止其他操作 - “main”线程的顶部--您可能会在这里控制崩溃,例如在内存耗尽时将一些数据转储到stdout。 - 如果您有一个“Runner”运行代码(例如,如果有人向您添加了监听器并且您调用了该监听器),则在运行代码时应捕获异常以记录问题并让您继续通知其他监听器。
对于这些情况,您始终希望捕获Exception(有时甚至是Throwable),以便捕获编程/意外错误、记录它们并继续执行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接