在处理异常时,涉及到很多相对性。 除了低级API覆盖从硬件和操作系统引发的错误的情况外,还存在一个模糊不清的领域,在这个领域中,程序员决定什么是异常,什么是正常条件。
你如何决定何时使用异常?是否有关于异常的一致政策?
在处理异常时,涉及到很多相对性。 除了低级API覆盖从硬件和操作系统引发的错误的情况外,还存在一个模糊不清的领域,在这个领域中,程序员决定什么是异常,什么是正常条件。
你如何决定何时使用异常?是否有关于异常的一致政策?
在对象内部的方法之间传递信息时,不应使用异常作为一种方法,而应该使用错误代码和防御性编程来处理本地情况。
异常的设计目的是将控制从检测到错误的点转移到可以处理错误的地方(堆栈更高的位置),假设由于局部代码没有足够的上下文来纠正问题,因此堆栈更高的位置将拥有更多的上下文,并能够更好地组织恢复。
在考虑异常时(至少在C++中),应考虑您的API提供的异常保证。最低级别的保证应该是基本保证,尽管在适当的情况下,应努力提供强保证。在您不使用特定API的任何外部依赖项的情况下,甚至可以尝试提供无抛出保证。
注意:不要将异常保证与异常规范混淆。
在异常发生后,无法保证对象的状态。 在这些情况下,不应再使用该对象。
几乎在所有情况下,这应该是方法提供的最低保证。 这保证了对象的状态是定义良好的,并且仍然可以一致地使用。
这保证了方法将完全成功执行, 或者抛出异常并且对象的状态不会更改。
该方法保证不允许异常传播到方法外部。 所有析构函数都应该提供此保证。
| 注意:如果一个异常在已经传播异常时从析构函数中逃逸
| 应用程序将终止。
致命错误:这些错误表示您的进程已经无法恢复。清理您可以的所有资源,但不要捕获它们。如果您编写的代码可以检测到这种情况,请将其抛出。例如:内存不足异常。
低级错误:相对简单的错误,表明您的进程无法处理被交付的任何数据,但是,如果引起该错误的情况被简单地忽略,则进程将继续正常运行。这些更为人所知的是 bug。不要抛出或捕获它们,而是通过传递错误或其他有意义的失败指示符来防止它们发生,这些指示符可以由您的方法处理。例如:空参数异常。
烦人错误:相对简单的错误,是您没有编写的其他代码抛出的。您必须捕获并处理所有这些错误,通常的方式与您自己的低级异常一样。请不要立即将它们再次抛出。例如:C# Int32.Parse() 方法中的格式异常。
外部错误:这些错误非常直接,看起来很像(来自其他人的代码)或甚至Boneheaded(来自您的代码)情况,但必须抛出,因为现实规定抛出这些错误的代码真的不知道如何恢复,但可能会由调用方处理。请放心抛出,但当您的代码从其他地方收到它们时,请捕获并处理它们。例如:文件未找到异常。
assert
吗? - VF1Never throw exceptions from destructors.
Maintain some basic level of exception guarantees about the state of the object.
Do not use exceptions to communicate errors which can be done using an error code unless it is a truly exception error and you might want the upper layers to know about it.
Do not throw exceptions if you can help it. It slows everything down.
Do not just catch(...)
and do nothing. Catch exceptions you know about or specific exceptions. At the very least log what happened.
When in the exception world use RAII because nothing is safe anymore.
Shipping code should not have suppressed exceptions at least with regards to memory.
When throwing exceptions package as much information as is possible along with it so that the upper layers have enough information to debug them.
Know about flags that can cause libraries like STL to throw exceptions instead of exhibiting unknown behaviour (e.g. invalid iterators / vector subscript overflow).
Catch references instead of copies of the exception object?
Take special care about reference counted objects like COM and warp them in reference counted pointers when working with code that could throw exceptions.
If a code throws an exception more than 2% of the time consider making it an error code for performance's sake.
Consider not throwing exceptions from undecorated dll exports / C interfaces because some compilers optimize by assuming C code to not throw exceptions.
If all that you do for handling exceptions is something akin to below, then don't use exception handling at all. You do not need it.
main
{
try {
all code....
}
catch(...) {}
}
异常处理会消耗大量的处理时间,因此只有在应用程序中发生真正不应该发生的情况时才应该抛出异常。
有时您可以预测可能会发生什么样的事情并编写代码进行恢复,在这种情况下,适当的方法是抛出和捕获异常、记录并恢复,然后继续执行。否则,它们应该仅用于处理意外情况并优雅地退出,同时尽可能捕获更多信息以帮助调试。
我是一名.NET开发人员,对于catch和throw,我的方法是:
我认为通常可以根据资源访问、数据完整性和数据有效性来确定异常的好方法。
访问异常
数据完整性
数据有效性
显然还有其他情况,但这些通常是我遵守需要的情况。
关于异常处理的策略,请参见:
http://henko.net/imperfection/exception-handling-policy-throwing-exception/。
(希望宣传网站不违反规定,但这里粘贴太多信息了。)
可能需要纠正/澄清一下,有一种被称为"契约驱动开发"(Contract-Driven Development)的策略,它在公共接口中明确记录每个方法的预期前置条件和保证后置条件。然后,当实现该方法时,任何阻止您满足合同中后置条件的错误都应该导致抛出异常。未满足前置条件被视为程序错误,应该导致程序停止。
我不确定契约驱动开发是否涉及捕获异常的问题,但一般来说,您只应该捕获您预期并可以合理恢复的异常。例如,大多数代码无法从内存不足异常中恢复,因此没有捕获的意义。另一方面,如果您尝试打开一个文件进行写入,您可以(并且应该)处理文件已被其他进程独占锁定或文件已被删除的情况(即使在尝试打开文件之前已检查了其存在性)。
正如另一位评论者指出的那样,您还应避免使用异常处理可以预期和避免的预期条件。例如,在.NET框架中,int.TryParse比使用try/catch的int.Parse更可取,特别是在循环或类似情况中使用时。