在使用 FXCop 分析一些遗留代码时,我突然想到在 try 块中捕获一般异常错误真的很糟糕,或者您是否应该寻找特定的异常。请在明信片上思考。
15 回答
显然,这是唯一真正答案是“视情况而定”的问题之一。
它主要取决于您在哪里捕获异常。一般来说,库在捕获异常方面应该更加保守,而在程序的顶层(例如,在您的 main 方法中或在控制器中的操作方法的顶部等),您可以更加自由地捕捉到什么。
这样做的原因是,例如,您不想捕获库中的所有异常,因为您可能会掩盖与您的库无关的问题,例如“OutOfMemoryException”,您真的希望它冒泡,以便用户可以通知等。另一方面,如果您正在讨论在 main() 方法中捕获异常,该方法捕获异常,显示它然后退出......好吧,在这里捕获几乎所有异常可能是安全的。
关于捕获所有异常的最重要的规则是你永远不应该默默地吞下所有异常......例如在Java中是这样的:
try {
something();
} catch (Exception ex) {}
或者在 Python 中:
try:
something()
except:
pass
因为这些可能是一些最难追踪的问题。
一个好的经验法则是,您应该只捕获您自己可以正确处理的异常。如果您不能完全处理异常,那么您应该让它冒泡给可以处理的人。
除非您在应用程序的前端进行一些日志记录和清理代码,否则我认为捕获所有异常是不好的。
我的基本经验法则是捕捉所有你期望的异常,其他的都是错误。
如果你抓住一切并继续前进,这有点像在汽车仪表板上的警告灯上贴上一块石膏。你再也看不到它了,但这并不意味着一切都好。
是的!(除了在您的应用程序的“顶部”)
通过捕获异常并允许代码继续执行,您说明您知道如何处理和规避或修复特定问题。你说这是一个可恢复的情况。捕获异常或系统异常意味着您将捕获诸如 IO 错误、网络错误、内存不足错误、缺少代码错误、空指针取消引用等问题。说你可以处理这些是骗人的。
在一个组织良好的应用程序中,这些不可恢复的问题应该在堆栈的高处进行处理。
此外,随着代码的发展,您不希望您的函数捕获将来添加到被调用方法的新异常。
在我看来,你应该捕获所有你期望的异常,但是这条规则适用于除了你的接口逻辑之外的任何东西。在调用堆栈的整个过程中,您可能应该创建一种方法来捕获所有异常,进行一些日志记录/提供用户反馈,并在需要和可能的情况下优雅地关闭。
没有什么比应用程序崩溃更糟糕的了,其中一些用户不友好的堆栈跟踪被转储到屏幕上。它不仅可以(可能是不需要的)深入了解您的代码,而且还会使您的最终用户感到困惑,有时甚至会将他们吓跑到竞争应用程序中。
关于这个问题有很多哲学讨论(更像是争论)。就个人而言,我相信你能做的最糟糕的事情就是吞下异常。下一个最糟糕的情况是允许异常冒泡到表面,用户会看到一个充满技术性的大屏幕的讨厌的屏幕。
好吧,我看不出捕获一般异常或特定异常之间有什么区别,除了当有多个 catch 块时,您可以根据异常的不同做出不同的反应。
总之,您将同时捕获IOException
和NullPointerException
使用泛型Exception
,但您的程序应该做出反应的方式可能不同。
我认为重点是双重的。
首先,如果您不知道发生了什么异常,您希望如何从中恢复。如果您预计用户可能会输入错误的文件名,那么您可以期待 FileNotFoundException 并告诉用户重试。如果相同的代码生成了 NullReferenceException,而您只是告诉用户再试一次,他们将不知道发生了什么。
其次,FxCop 指南确实侧重于库/框架代码 - 并非所有规则都旨在适用于 EXE 或 ASP.Net 网站。因此,拥有一个可以记录所有异常并很好地退出应用程序的全局异常处理程序是一件好事。
捕获所有异常的问题在于,您可能正在捕获您不期望的异常,或者实际上是您不应该捕获的异常。事实是,任何类型的异常都表明出现了问题,您必须在继续之前对其进行整理,否则您最终可能会遇到数据完整性问题和其他不易追踪的错误。
举一个例子,在一个项目中,我实现了一种称为 CriticalException 的异常类型。这表示需要开发人员和/或管理人员干预的错误情况,否则客户会得到错误的计费,或者可能导致其他数据完整性问题。它也可以用于其他类似情况,仅记录异常是不够的,需要发送电子邮件警报。
另一位没有正确理解异常概念的开发人员随后将一些可能引发此异常的代码包装在一个丢弃所有异常的通用 try...catch 块中。幸运的是,我发现了它,但它可能会导致严重的问题,特别是因为它应该捕获的“非常罕见”的极端情况比我预期的要普遍得多。
所以一般来说,除非你 100% 确定你确切地知道在什么情况下会抛出哪些类型的异常,否则捕获泛型异常是不好的。如果有疑问,让他们冒泡到顶级异常处理程序。
这里有一个类似的规则是永远不会抛出 System.Exception 类型的异常。您(或其他开发人员)可能希望在调用堆栈的更高位置捕获您的特定异常,同时让其他人通过。
(但是有一点需要注意。在 .NET 2.0 中,如果线程遇到任何未捕获的异常,它会卸载整个应用程序域。因此,您应该将线程的主体包装在通用的 try...catch 块中并传递您的全局异常处理代码中捕获的任何异常。)
我想扮演魔鬼的拥护者来捕捉异常并记录它并重新抛出它。这可能是必要的,例如,如果您在代码中某处并且发生意外异常,您可以捕获它,记录在简单堆栈跟踪中不可用的有意义的状态信息,然后将其重新抛出到上层以处理。
有两个完全不同的用例。第一个是大多数人正在考虑的,在一些需要检查异常的操作周围放置一个 try/catch。无论如何,这不应该是包罗万象的。
然而,第二个是在程序可以继续时阻止程序中断。这些情况是:
- 所有线程的顶部(默认情况下,异常会消失得无影无踪!)
- 在您希望永远不会退出的主处理循环中
- 在循环中处理对象列表,其中一个故障不应阻止其他对象
- “主”线程的顶部——您可能会控制这里的崩溃,例如当内存不足时将一些数据转储到标准输出。
- 如果您有一个运行代码的“Runner”(例如,如果有人向您添加了一个侦听器并且您调用了该侦听器),那么当您运行代码时,您应该捕获 Exception 以记录问题并让您继续通知其他侦听器。
在这些情况下,您总是希望捕获异常(有时甚至可以抛出)以捕获编程/意外错误,记录它们并继续。
不受欢迎的意见:不是。
捕获所有可以有意义地从中恢复的错误。有时这就是全部。
根据我的经验,异常来自哪里比实际抛出哪个异常更重要。如果你把你的异常限制在狭窄的地方,你通常不会吞下任何有用的东西。大多数以错误类型编码的信息都是辅助信息,因此无论如何您通常最终都会有效地捕获所有这些信息(但您现在必须查找 API 文档以获取可能的异常的全部集合)。
请记住,几乎在每种情况下,一些例外情况都应该出现在顶部,例如 PythonKeyboardInterrupt
和SystemExit
. 对 Python 来说幸运的是,它们保存在异常层次结构的一个单独分支中,因此您可以通过捕获Exception
. 精心设计的异常层次结构使这类事情变得非常简单。
捕获一般异常会导致严重问题的主要时间是在处理需要清理的资源时(可能在finally
子句中),因为包罗万象的处理程序很容易错过这类事情。defer
幸运的是,对于具有、 Pythonwith
或 C++ 和 Rust 中的 RAII等结构的语言来说,这并不是真正的问题。
我认为一个好的指导方针是只从框架内捕获特定异常(以便主机应用程序可以处理诸如磁盘填充等边缘情况),但我不明白为什么我们不应该能够捕获所有我们的应用程序代码中的异常。很简单,有时您不希望应用程序崩溃,无论出现什么问题。
大多数时候不需要捕获一般异常。当然,在某些情况下您别无选择,但在这种情况下,我认为最好检查一下为什么需要抓住它。也许你的设计有问题。
抓住一般的例外,我感觉就像在燃烧的建筑物内拿着一根炸药,然后熄灭引信。它会在短时间内有所帮助,但炸药无论如何都会在一段时间后爆炸。
当然,在某些情况下可能需要捕获一般异常,但仅用于调试目的。错误和错误应该被修复,而不是隐藏。
对于我在应用内计费中使用的 IabManager 类(来自 TrivialDrive 在线示例),我注意到有时我会处理很多异常。它已经到了不可预测的地步。
我意识到,只要我在一个异常发生后停止尝试消费应用内产品,这是大多数异常发生的地方(消费,而不是购买),我就安全了。
我只是把所有的异常都改成了一般的异常,现在我不用担心会抛出任何其他随机的、不可预测的异常。
前:
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();
}
后:
catch (final Exception exc)
{
exc.printStackTrace();
}