处理异常涉及很多相对性.除了低级别的API,其中异常包括从硬件和操作系统引发的错误,有一个阴暗的区域,程序员决定什么构成异常,什么是正常情况.
你如何决定何时使用例外?您是否有关于例外的一致政策?
异常应该不被用作方法之间内部传递的对象内部的信息,在本地你应该使用错误代码和防御式编程的方法.
异常旨在将控制从检测到错误的位置传递到可以处理错误的位置(堆栈中的较高位置),可能是因为本地代码没有足够的上下文来纠正问题以及堆栈中的更高位置将有更多的背景,从而能够更好地组织恢复.
在考虑异常时(至少在C++中),您应该考虑API所做的异常保证.尽管您应该(在适当的情况下)努力提供有力的保证,但最低保证水平应该是基本保证.如果您不使用关节API的外部依赖关系,您甚至可以尝试提供无投掷保证.
NB不要将异常保证与异常规范混淆.
异常转义方法后,无法保证对象的状态在这些情况下,不应再使用该对象.
在几乎所有情况下,这应该是方法提供的最小保证.这可以保证对象的状态定义良好,并且仍然可以一致地使用.
这可以保证方法完全成功或者抛出异常并且对象状态不会改变.
该方法保证不允许任何异常传播出该方法.所有析构函数都应该做出这种保证.
| NB如果一个异常逃离析构函数时异常已经传播
| 申请将终止
Microsoft的高级软件设计工程师Eric Lippert撰写的这篇博客文章总结了一套优秀而简短的例外策略指南.
简而言之:
致命:可怕的错误表明您的过程完全无法恢复.清理你可以使用的任何资源,但不要抓住它们.如果您正在编写能够检测到这种情况的代码,请务必抛出.示例:内存不足异常.
Boneheaded:相对简单的错误,指示您的进程无法对其传递的任何数据进行操作,但如果导致错误的任何情况被忽略,则会正常继续.这些被称为错误.不要抛弃或捕获它们,而是防止它们发生,通常是通过传递错误或其他有意义的失败指标,这些指标可以由您的方法处理.示例:Null参数异常.
Vexing:你不拥有的代码相对简单的错误就是向你投掷.你必须抓住所有这些并处理它们,通常与处理你自己的一个Boneheaded例外的方式相同.请不要再把它们扔掉.示例:从C#的Int32.Parse()方法格式化异常
外生:相对简单的错误看起来很像Vexing(来自其他人的代码)甚至是Boneheaded(来自你的代码)情况,但必须抛出,因为现实要求投掷它们的代码真的不知道如何恢复,但是来电者可能会.继续抛出这些,但是当你的代码从别处收到它们时,抓住它们并处理它们.示例:找不到文件异常.
在这四个中,外生的是那些你必须考虑最多才能做到正确的.指示找不到文件的异常适合抛出IO库方法,因为该方法几乎肯定不会知道如果找不到文件该怎么办,特别是考虑到情况可能随时发生而且那里无法检测情况是否是暂时的.但是,抛出这样的异常并不适合应用程序级代码,因为该应用程序可以从用户那里获取有关如何继续的信息.
永远不要从析构函数中抛出异常.
保持关于对象状态的一些基本异常保证级别.
不要使用异常来传达可以使用错误代码完成的错误,除非它是一个真正的异常错误,您可能希望上层知道它.
如果可以提供帮助,请不要抛出异常.它减慢了一切.
不要只做catch(...)
什么都不做.捕获您了解的异常或特定异常.至少记录发生的事情.
在异常世界中使用RAII因为没有什么是安全的.
运输代码不应该至少在内存方面抑制异常.
在抛出异常包时尽可能多地提供信息,以便上层有足够的信息来调试它们.
了解可能导致像STL这样的库抛出异常而不是表现出未知行为的标志(例如,无效的迭代器/向量下标溢出).
捕获引用而不是异常对象的副本?
在处理可能引发异常的代码时,请特别注意引用计数对象(如COM)并在引用计数指针中对它们进行扭曲.
如果代码在超过2%的时间内抛出异常,请考虑将其作为错误代码,以提高性能.
考虑不从未修饰的dll导出/ C接口抛出异常,因为一些编译器通过假设C代码不抛出异常来优化.
如果您为处理异常所做的一切都类似于下面的内容,那么根本不要使用异常处理.你不需要它.
main { try { all code.... } catch(...) {} }