处理异常涉及很多相关性。除了低级别的API,其中异常包括从硬件和操作系统引发的错误,有一个阴暗的区域,程序员决定什么构成异常和什么是正常情况。
您如何决定何时使用例外? 您是否有关于例外的一致政策?
答案 0 :(得分:30)
异常应该不用作在对象内部方法之间内部传递信息的方法,在本地你应该使用错误代码和防御性编程。
异常旨在将控制从检测到错误的位置传递到可以处理错误的位置(堆栈上方),可能是因为本地代码没有足够的上下文来纠正问题,而更高的位置堆栈将有更多的上下文,从而能够更好地组织恢复。
在考虑异常时(至少在C ++中),您应该考虑API所做的异常保证。尽管您应该(在适当的情况下)努力提供有力的保证,但最低保证水平应该是基本保证。如果您不使用关节API的外部依赖关系,您甚至可以尝试提供无投掷保证。
N.B。不要将异常保证与异常规范混淆。
在异常转义方法后,无法保证对象的状态 在这些情况下,不应再使用该对象。
在几乎所有情况下,这应该是方法提供的最低保证。 这可以保证对象的状态定义良好,并且仍可以一直使用。
这可以保证该方法完全成功 或者抛出异常并且对象状态不会改变。
该方法保证不允许任何异常传播出该方法。 所有的破坏者都应该做出这种保证 |注:如果异常已经传播,则异常会转义析构函数 |申请将终止
答案 1 :(得分:7)
Microsoft的高级软件设计工程师Eric Lippert撰写的这篇博客文章总结了一套优秀而简短的exception strategy guidelines。
简而言之:
致命:可怕的错误表明您的流程完全无法恢复。清理你可以使用的任何资源,但不要抓住它们。如果您正在编写能够检测到这种情况的代码,请务必抛出。示例:内存不足异常。
Boneheaded :相对简单的错误,表明您的进程无法对其传递的任何数据进行操作,但如果导致错误的任何情况被忽略,则会正常继续。这些更好地称为错误。不要抛弃或捕获它们,而是防止它们发生,通常是通过传递错误或其他有意义的失败指标,这些指标可以由您的方法处理。示例:空参数例外。
烦恼:您不拥有的代码相对简单的错误就是向您投掷。您必须捕获所有这些并处理它们,通常与处理您自己的 Boneheaded 异常的方式相同。请不要再把它们扔掉。示例:来自C#的Int32.Parse()方法
外生:相对简单的错误看起来很像 Vexing (来自其他人的代码)甚至 Boneheaded (来自你的代码)情况,但必须抛出,因为现实规定抛出它们的代码实际上不知道如何恢复,但调用者可能会。继续抛出这些,但是当你的代码从别处收到它们时,抓住它们并处理它们。示例:找不到文件异常。
在这四个中,外源性的是你必须考虑最多才能做到的。指示找不到文件的异常适合抛出IO库方法,因为该方法几乎肯定不会知道如果找不到文件该怎么办,特别是考虑到情况可能随时发生而且那里无法检测情况是否是暂时的。但是,抛出这样的异常不适合应用程序级代码,因为该应用程序可以从用户那里获取有关如何继续的信息。
答案 2 :(得分:6)
永远不要从析构函数中抛出异常。
维护关于对象状态的一些基本级别的异常保证。
不要使用异常来传达可以使用错误代码完成的错误,除非它是一个真正的异常错误,您可能希望上层知道它。
如果可以提供帮助,请不要抛出异常。它减慢了一切。
不只是catch(...)
而且什么都不做。捕获您了解的异常或特定异常。至少记录发生的事情。
在异常世界中使用RAII因为没有什么是安全的。
运输代码不应该至少在内存方面有抑制异常。
在抛出异常包时尽可能多地提供信息,以便上层有足够的信息来调试它们。
了解可能导致像STL这样的库抛出异常而不是表现出未知行为的标志(例如无效的迭代器/向量下标溢出)。
捕获引用而不是异常对象的副本?
在处理可能引发异常的代码时,请特别注意引用计数对象(如COM)并在引用计数指针中对它们进行扭曲。
如果代码在超过2%的时间内抛出异常,请考虑将其作为错误代码,以提高性能。
考虑不从未修饰的dll导出/ C接口抛出异常,因为某些编译器通过假设C代码不会抛出异常来进行优化。
如果您为处理异常所做的一切都类似于下面的内容,那么根本不要使用异常处理。你不需要它。
main
{
try {
all code....
}
catch(...) {}
}
答案 3 :(得分:3)
例外处理时间非常昂贵,因此只有当您的应用中确实不应发生某些事情时才会抛出它们。
有时您可以预测可能发生的事情以及从中恢复的代码,在这种情况下,抛出并捕获异常,记录和恢复,然后继续是合适的。否则它们应该只用于处理意外情况并优雅地退出,同时捕获尽可能多的信息以帮助调试。
我是.NET开发人员,对于catch和throw,我的方法是:
答案 4 :(得分:2)
这个答案的上下文是Java语言。
对于可能弹出的正常错误,我们直接处理这些错误(例如,如果某些内容为空,则为空,等等)。我们只对异常情况使用实际例外。
但是,我们不会抛出已检查的异常。我们将RuntimeException子类化为我们自己的特定异常,在适用的地方直接捕获它们,对于其他库,JDK API等抛出的异常,我们在内部尝试/ catch并记录异常(如果发生的事情真的不应该没有办法像批处理作业的文件未找到异常那样恢复,或者我们在RuntimeException中包装异常然后抛出它。在代码的外部,我们依赖异常处理程序来最终捕获RuntimeException,无论是JVM还是Web容器。
这样做的原因是它避免了在你可能有四个调用方法的实例的地方创建强制try / catch块,但只有一个实际上可以处理异常。这似乎是规则,而不是(没有双关语... ouch)异常,所以如果第四个可以处理它,它仍然可以捕获它并检查异常的根本原因以获得发生的实际异常(不必担心RuntimeException包装器。)
答案 5 :(得分:2)
我认为通常有一种很好的方法可以根据对资源的访问,数据的完整性和数据的有效性来确定异常。
访问例外
数据完整性
数据的有效性
显然还有其他情况,但这些通常是我试图遵守的地方。
答案 6 :(得分:2)
我认为使用例外的最佳方式取决于您使用的计算机语言。例如,Java比C ++具有更加可靠的异常实现。
如果您使用的是C ++,我建议您至少尝试阅读Bjarne Stroustrup(C ++的发明者)对异常安全性所说的内容。请参阅他的书“The C ++ programming language”中的appendix E。
他花了34页试图解释如何以安全的方式处理异常。如果您确实理解了他的建议,那么这应该是您需要知道的全部内容。
答案 7 :(得分:0)
其他人可能必须纠正/澄清这一点,但是有一种叫做(我相信)“合同驱动的开发”的策略,你在公共接口中明确记录每种方法的预期前提条件,以及保证后的条件。条件。然后,在实现该方法时,任何阻止您满足合同中的后置条件的错误都应该导致抛出异常。未能满足前提条件被视为程序错误,应导致程序中止。
我不确定合同驱动的开发是否涉及捕获异常的问题,但一般来说,您应该只捕获您期望的并且可以合理地从中恢复的异常。例如,大多数代码无法从Out Of Memory异常中有意义地恢复,因此没有必要捕获它。另一方面,如果您尝试打开文件进行写入,则可以(并且应该)处理文件被另一个进程独占锁定的情况,或者文件已被删除的情况(即使您检查了它在试图打开它之前存在。)
正如另一位评论者所指出的那样,您还应避免使用异常来处理可预期和避免的预期条件。例如,在.NET框架中,int.TryParse优于使用try / catch的int.Parse,尤其是在循环中使用时。
答案 8 :(得分:0)
答案 9 :(得分:0)
作为一名C ++开发人员,我自己的策略不是将异常从我认为是公共api的异常抛给我的类/模块(实际上是COM的一个要求)。但是,我在私有类实现中广泛使用异常。例如,使用ATL:
HRESULT Foo()
{
HRESULT hr = S_OK;
try {
// Avoid a whole lot of nested ifs and return code
// checking - internal stuff just throws.
DoStuff();
DoMoreStuff(); // etc.
} catch ( CAtlException& e ) {
hr = e;
}
return hr;
}
void DoSomething()
{
// If something goes wrong, AtlThrow( E_FAILED or E_WHATEVER );
}
答案 10 :(得分:0)
我的异常处理政策可以在以下网址找到:
http://henko.net/imperfection/exception-handling-policy-throwing-exception/
(希望它不违反推广网站的规则,但要粘贴这里有太多信息。)
答案 11 :(得分:-1)
根据规范,语言环境不会引发异常。所使用的语言是否确实具有例外的概念?我想在Java中“除以零”,或者在Ada中使用CONSTRAINT_ERROR而在C中没有任何内容。
程序员如何在选择在其构成中定义了异常的编程语言后“决定”使用异常?
编辑:或者不是“使用”例外,是否意味着什么时候有关于“处理”例外的一致且一致的政策?
Edit2:您可以查看Steven Dewhurst的书“C ++ Gotchas”中的free chapter,特别是Gotcha 64和Gotcha 65.虽然它专注于C ++,但所涉及的课程在其他语言中很有用。 / p>