捕获一般异常真的那么糟糕吗?

Is it really that bad to catch a general exception?

在用fxcop分析某些遗留代码时,我突然想到,在try块中捕获一般的异常错误真的是那么糟糕,还是应该查找特定的异常。请考虑一下明信片。


很明显,这是唯一真正的答案是"视情况而定"的问题之一。

它依赖的主要内容是在哪里捕获异常。一般来说,库应该更保守,只捕获异常,而在程序的顶层(例如,在主方法中或在控制器中的操作方法的顶层等),您可以更自由地捕获所捕获的内容。

这样做的原因是,例如,您不想捕获库中的所有异常,因为您可能会掩盖与库无关的问题,例如"OutofMemoryException",您实际上更喜欢气泡化,以便通知用户,等等。另一方面,如果您在讨论捕获主()中的异常方法捕获异常并显示它,然后退出…好吧,在这里抓住任何例外都是安全的。

关于捕获所有异常的最重要的规则是,您不应该只是默默地吞下所有异常…例如Java中的这种情况:

1
2
3
try {
    something();
} catch (Exception ex) {}

或者在python中:

1
2
3
4
try:
    something()
except:
    pass

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

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


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

我的基本经验法则是捕获所有你期望的异常,其他的都是bug。

如果你抓住一切继续前进,就有点像在你的汽车仪表板上的警示灯上贴了一层灰泥。你看不到了,但这并不意味着一切都好。


对!(除了在申请表的"顶部")。

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

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

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


在我看来,您应该捕获您期望的所有异常,但是这个规则适用于除接口逻辑之外的任何东西。在调用堆栈的整个过程中,您可能应该创建一种方法来捕获所有异常,进行一些日志记录/向用户提供反馈,并且,如果需要和可能,可以优雅地关闭。

没有什么比应用程序与一些用户不友好的stacktrace崩溃更糟糕的了。它不仅能让(可能是不需要的)洞察您的代码,而且还会混淆您的最终用户,有时甚至会把他们吓跑到竞争对手的应用程序。


关于这个问题有很多哲学上的讨论(更像是论据)。就我个人而言,我认为你能做的最糟糕的事情就是接受例外。下一个最糟糕的情况是允许一个例外浮出水面,在那里用户会看到一个满是技术性胡言乱语的令人讨厌的屏幕。


捕获所有异常的问题是,您可能正在捕获您不期望的异常,或者确实是不应该捕获的异常。事实上,任何类型的异常都表明出了问题,您必须在继续之前对其进行排序,否则最终可能会出现数据完整性问题和其他不容易跟踪的错误。

举个例子,在一个项目中,我实现了一个称为CriticalException的异常类型。这表示需要开发人员和/或管理人员干预的错误情况,否则客户将收到错误的账单,或者可能导致其他数据完整性问题。当仅仅记录异常是不够的,并且需要发送电子邮件警报时,它也可以用于其他类似的情况。

另一个没有正确理解异常概念的开发人员随后包装了一些代码,这些代码可能会在一个通用的try…catch块中引发此异常,该块丢弃了所有异常。幸运的是,我发现了它,但它可能会导致严重的问题,特别是因为它本应捕获的"非常罕见"的角落案件比我预期的要普遍得多。

因此,一般来说,捕获一般异常是不好的,除非您100%确信您确切地知道将抛出哪种异常以及在哪种情况下抛出。如果有疑问,让它们冒泡到顶级异常处理程序。

这里的一个类似规则是从不引发System.Exception类型的异常。您(或另一个开发人员)可能希望在调用堆栈的更高位置捕获特定的异常,同时让其他人通过。

(不过,有一点需要注意。在.NET 2.0中,如果线程遇到任何未捕获的异常,它将卸载整个应用程序域。因此,您应该将线程的主体包装在一个通用的try…catch块中,并将捕获的任何异常传递给全局异常处理代码。)


我认为这一点是双重的。

首先,如果您不知道发生了什么异常,您如何希望从中恢复。如果预期用户可能键入错误的文件名,则可以预期出现fileNotFoundException,并告诉用户重试。如果相同的代码生成了nullreferenceexception,而您只是告诉用户再试一次,他们就不知道发生了什么。

第二,fxcop指南确实侧重于库/框架代码——并不是所有规则都设计为适用于exe或asp.net网站。因此,拥有一个全局异常处理程序来记录所有异常并很好地退出应用程序是一件好事。


好吧,我看不出捕获一般异常和特定异常之间有什么区别,除了当有多个catch块时,您可以根据异常的内容做出不同的反应。

总之,您将使用通用的Exception捕获IOExceptionNullPointerException,但程序的反应方式可能不同。


我想扮演恶魔拥护者的角色,捕捉异常并将其记录下来,然后重新处理它。例如,如果您在代码中的某个地方发生了意外的异常,您可以捕获它,记录简单堆栈跟踪中不可用的有意义的状态信息,然后将其重新传输到上层进行处理,这是必需的。


有两个完全不同的用例。第一个问题是大多数人都在考虑的问题,即在需要检查异常的操作周围放置一个try/catch。无论如何,这不应该是一个万不得已的局面。

然而,第二个方法是在程序可以继续运行时阻止它中断。这些情况包括:

  • 所有线程的顶部(默认情况下,异常将消失,无跟踪!)
  • 在您期望永远不会退出的主处理循环中
  • 在一个循环中,处理一个对象列表,其中一个失败不应停止另一个失败。
  • 在"主"线程的顶部——您可以在这里控制崩溃,比如在内存不足时将一些数据转储到stdout。
  • 如果您有一个运行代码的"运行者"(例如,如果有人向您添加了一个侦听器,而您调用了该侦听器),那么当您运行代码时,应该捕获异常以记录问题并让您继续通知其他侦听器。

这些情况下,您总是希望捕获异常(有时甚至可以丢弃),以便捕获编程/意外错误,记录它们并继续。


不受欢迎的意见:不是真的。

捕获您可以有意义地从中恢复的所有错误。有时候这就是全部。

在我的经验中,异常来自哪里比实际抛出的异常更重要。如果你把你的例外情况控制在很小的范围内,你通常不会吞下任何有用的东西。编码在错误类型中的大多数信息都是辅助信息,因此通常情况下,您最终还是能够有效地捕获所有这些信息(但现在您必须查找API文档以获取可能的全部异常)。

请记住,在几乎每种情况下都会出现一些异常,比如python的KeyboardInterruptSystemExit。对于Python来说,幸运的是,它们被保存在异常层次结构的一个单独的分支中,因此您可以通过捕获Exception让它们冒泡。设计良好的异常层次结构使得这类事情非常简单。

捕获一般异常将导致严重问题的主要时间是在处理需要清理的资源时(可能在finally子句中),因为catch all处理程序很容易错过这类事情。幸运的是,对于EDOCX1的4种语言来说,这不是一个真正的问题,比如Python的EDCOX1 5的构造,或者C++中的RAII和锈迹。


对于我的IABManager类,我使用了应用程序内计费(来自在线的Trivialdrive示例),我注意到有时我会处理很多异常情况。到了无法预测的地步。

我意识到,只要我在一个例外发生后停止尝试使用应用内产品的尝试,那就是大多数例外都会发生的地方(在消费中,而不是购买),我就安全了。

我只是把所有的异常都改成了一般的异常,现在我不必担心其他随机的、不可预测的异常会被抛出。

之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    catch (final RemoteException exc)
    {
        exc.printStackTrace();
    }
    catch (final IntentSender.SendIntentException exc)
    {
        exc.printStackTrace();
    }
    catch (final IabHelper.IabAsyncInProgressException exc)
    {
        exc.printStackTrace();
    }
    catch (final NullPointerException exc)
    {
        exc.printStackTrace();
    }
    catch (final IllegalStateException exc)
    {
        exc.printStackTrace();
    }

后:

1
2
3
4
    catch (final Exception exc)
    {
        exc.printStackTrace();
    }

捕捉到一般的例外,我感觉就像在燃烧的大楼里拿着一根炸药棒,然后熄灭引信。这有助于短期,但炸药会在一段时间后爆炸。

在corse中,可能存在需要捕获一般异常的情况,但仅用于调试目的。错误和错误应该被修复,而不是隐藏。


大多数时候不需要捕获一般的异常。当然,有些情况下你没有选择的余地,但在这种情况下,我认为最好检查一下为什么你需要抓住它。也许你的设计有问题。


我认为一个好的指导方针是只捕获框架内的特定异常(这样宿主应用程序就可以处理边缘情况,如磁盘填充等),但我不明白为什么我们不能捕获应用程序代码中的所有异常。很简单,有时候你不希望应用崩溃,不管出什么问题。