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