我一直相信,如果一个方法可以抛出一个异常,那么用一个有意义的try块来保护这个调用是鲁莽的。
我刚刚发布了"你应该总是把电话包装起来,这样就可以尝试一下,抓住障碍。"对于这个问题,有人告诉我这是"非常糟糕的建议"——我想知道为什么。
- 如果我知道一个方法会抛出一个异常,我会首先纠正它。因为我不知道代码在哪里以及为什么会抛出异常,所以我在源代码处捕获了异常(每个方法-一个泛型try catch)。例如,一个团队给了我一个带有早期数据库的代码库。在SQL层中添加try catch后,许多列丢失并被捕获。其次,我可以记录方法名和消息,用于离线调试——否则我不知道它是从哪里开始的。
方法只应在能够以某种合理的方式处理异常时捕获异常。
否则,向上传递它,希望调用堆栈上更高的方法能够理解它。
正如其他人所指出的,在调用堆栈的最高级别有一个未处理的异常处理程序(带有日志记录),以确保记录任何致命的错误是一种良好的做法。
- 同样值得注意的是,try块的成本(从生成的代码来看)。在Scott Meyers的"更有效的C++"中有一个很好的讨论。
- 事实上,在任何现代的C编译器中,try块都是免费的,这些信息都可以追溯到nick。我也不同意使用顶级异常处理程序,因为您会丢失位置信息(指令失败的实际位置)。
- @盲目地:顶级异常处理程序不存在处理异常的功能,但实际上是大声喊出有一个未处理的异常,给出它的消息,以一种优雅的方式结束程序(返回1而不是调用terminate)。它更像是一个安全机制。另外,在没有任何例外的情况下,try/catch或多或少是免费的。当有一个传播的时候,它每次抛出和捕获都会消耗时间,所以一个只有rethrow的try/catch链并不是免费的。
- @克里斯托弗:完全同意顶级日志记录。在记录错误后,不要让程序继续运行!程序应该在此时使用核心转储终止。
- @克里斯托弗这有一些优势,但也有一个可怕的缺点。您已经丢失了引发异常的堆栈。如果可以处理异常,请使用try catch,但如果不知道如何解决问题,则不要使用它来避免程序终止。
- 我认为很明显,我打算在"顶级处理程序"记录异常信息后终止程序。关于"丢失堆栈":如果程序因异常而终止,您不会丢失它吗?C++不象Java:当它死时,它不能给你一个很好的堆栈跟踪(除非你正在调试或者你的平台对这类事情有特殊的支持)。
- 我不同意你总是在意外的情况下崩溃。现代软件设计是很有条理的,所以为什么要惩罚应用程序的其余部分(更重要的是,用户!)只是因为有一个错误?让你最不想做的事情崩溃,至少试着给用户一些小的代码窗口,让他们保存工作,即使应用程序的其余部分无法访问。
- 我也同意,但我也要补充一点,即使函数不捕获异常,甚至不重新引发异常,它们也应该记住异常。在编写任何函数时,您可能需要考虑并记录三个异常保证。
- kendall:如果一个异常到达一个顶级处理程序,那么您的应用程序按定义处于未定义的状态。尽管在某些特定的情况下,保留用户数据可能有价值(考虑到Word的文档恢复),但程序不应覆盖任何文件或提交到数据库。
- try块在所有编译器上当然不是免费的。具体来说,32位msvc编译器只为try块添加了相当多的开销(我见过测试将其固定在应用程序总运行时间的10-15%左右)。其他一些编译器使用另一个异常实现来避免运行时开销,但在Exchange中使用了更多的内存。不管怎样,都会有代价,即使从来没有抛出异常。
- @杰尔夫:我有一种印象,那就是掷骰子比较贵。如果不抛出异常,则开销极低。
- @米奇:取决于你对"极端"的定义。:)这取决于编译器是如何实现它的,正如我所说,有两种策略取决于您是想牺牲执行时间还是内存,但是必须设置相当多的管道来跟踪存在哪些异常处理程序,在抛出异常时跳转到何处,如何展开堆栈以及等等。当然,当实际抛出异常时,它需要额外的成本,但是仅仅拥有一个try块也需要一些成本。
- (但和往常一样,在分析显示这是一个问题之前不要太担心)
- "kendallhelmstettergelner:一个伟大的failings对Java和.net这是他们除面向提供好的手段,distinguishing 99 %的病例在西安EXCEPTION均值,例如"文件不可能是T型设计的一些原则,"从1 %,在它的均值的"CPU起火"。他们也让awkward A型这应该用更多的widely:如果一个代码块中的某一puts热情inconsistent态,然后puts回来,安unexpected例外,occurs在那块对代码不"是"被抓住",但应该expressly invalidate面向。
- "kendallhelmstettergelner:这主意unexpected exceptions均的事情是在一undefined州立大学在Stearns从一个失败的事情,这是事情的ensure expressly INVALID if any exceptions,预期的或不可能的,否则,把他们在一个corrupted态。如果在堆栈中的课程unwinding invalidated对象的get abandoned,normally应继续执行。如果corrupted转弯的收购对象是重要的操作程序,下一尝试使用它们的应力一停。在"类型"的研究,除occurred有小做与适当的处理thereof。。。。。。。
- try块是免费的(除系统与硬件的支持,这是近全)…………………但他们创造的新分支。这使对优化器的工作努力,尽力使一些优化的outright不可能的。例如,我相信这是可能的nrvo T在在场的代码所使用exceptions(编译器不允许T的写入来电的变量,如果return声明并不是真的reached T)。
- 怎么会,我确定如果一个方法可以或应该handle的例外???????为什么不只是计划的一种方法有能力对其exceptions to handle
- 这是更通常的方法调用的代码to handle的例外(在meaningful),因为它"知道"的背景下,在这一种方法被称为是一种方法,而将不会。
- 盲汉说:"这一现代的C编译器的"handles try块吗?
- "lightnessracesinorbit,obviously A typo,制造过四年前。这已经是一statute研究的局限性是这类的事情。
- 盲汉:"为什么?"??????????????
- "lightnessracesinorbit,在其他的事情,我不anymore编辑它。什么是exactly点在bringing弹出它吗?besides感觉更好的关于你自己,我的意思。
- 盲汉"的局面:我看到的relevance大学如何在《评论的是。。。。。。。你wrote它和它仍然是可见的。它可以mislead人。我想它的价值从一简短的谈话来验证,这是unintentional,所以,如果它是不那么我可以educate你和使你变成一个稍长的更多的有知识的人。和,如果它是,那么至少其他用户的利益,从belated校正。对不起,如果你觉得我已经不知什么原因,"伤害",你在任何通,但发射攻击个人的热情看来,不必要的,没有它的T???????
正如米奇和其他人所说,你不应该抓住一个你不打算以某种方式处理的例外。在设计应用程序时,您应该考虑应用程序将如何系统地处理异常。这通常会导致基于抽象的错误处理层——例如,您在数据访问代码中处理所有与SQL相关的错误,这样,与域对象交互的应用程序部分就不会暴露出这样一个事实,即在某个地方有一个数据库。
除了"随处可见"的气味之外,还有一些相关的代码气味是您绝对想要避免的。
"catch,log,rethrow":如果您想要基于作用域的日志记录,那么在堆栈由于异常而展开时,在其析构函数中编写一个发出日志语句的类(ala std::uncaught_exception())。您所需要做的只是在您感兴趣的范围内声明一个日志实例,voila,您有日志记录,并且没有不必要的try/catch逻辑。
"catch,throw translated":这通常指向一个抽象问题。除非您正在实现一个联邦解决方案,将几个特定的异常转换为一个更通用的异常,否则您可能有一个不必要的抽象层…不要说"我明天可能需要它"。
"接住,清理,再流":这是我的一个宠物尿。如果您看到很多这样的情况,那么您应该应用资源获取IS初始化技术,并将清除部分放在一个看门人对象实例的析构函数中。
我认为代码中充斥着try/catch块,是代码审查和重构的良好目标。它表明要么异常处理不被很好地理解,要么代码已成为AM&339;BA,并且严重需要重构。
- # 1我们是新的。+ 1所说的。也,我注意到一个D类除普通的# 2,这是如果你是一个图书馆的设计常常准备你会想要translate内部exceptions激情的东西specified通过你的库的接口来减少耦合(这可能是什么,你均通过"federated解决方案",但我是不familiar与这项)。
- 基本上,你所说的:PARASIFIFT。COM/C++-FAQLIT/ExtExpices。
- #2,如果它不是代码味道,但有意义,可以通过将旧的异常保持为嵌套的异常来进行增强。
- 关于1:std::uncaught_Exception()告诉您飞行中有一个未捕获的异常,但是afaik只有catch()子句可以让您确定该异常实际上是什么。因此,虽然您可以记录由于未捕获的异常而退出作用域的事实,但是只有封闭的try/catch允许您记录任何详细信息。对的?
- @杰里米-你是对的。我通常在处理异常时记录异常详细信息。跟踪中间帧是非常有用的。您通常需要记录线程标识符或一些标识上下文,以及关联日志行。我使用了一个与log4j.Logger类似的Logger类,该类在每个日志行中都包含线程ID,并在异常活动时在析构函数中发出警告。
因为下一个问题是"我发现了一个例外,接下来我该怎么做?"你会怎么做?如果你什么都不做,那就是错误隐藏,程序可能"只是不工作",没有任何机会发现发生了什么。你需要明白一旦你发现了异常,你会做什么,只有当你知道的时候才会知道。
赫伯·萨特在这里写到了这个问题。值得一读。戏弄者:
"Writing exception-safe code is fundamentally about writing 'try' and 'catch' in the correct places." Discuss.
Put bluntly, that statement reflects a fundamental misunderstanding of exception safety. Exceptions are just another form of error reporting, and we certainly know that writing error-safe code is not just about where to check return codes and handle error conditions.
Actually, it turns out that exception safety is rarely about writing 'try' and 'catch' -- and the more rarely the better. Also, never forget that exception safety affects a piece of code's design; it is never just an afterthought that can be retrofitted with a few extra catch statements as if for seasoning.
您不需要用try catch覆盖每个块,因为try catch仍然可以捕获在调用堆栈的后面的函数中抛出的未处理异常。因此,您可以在应用程序的顶层逻辑中拥有一个函数,而不是让每个函数都有一个try catch。例如,可能有一个SaveDocument()顶级例程,它调用许多调用其他方法等的方法。这些子方法不需要自己的Try捕获,因为如果它们抛出,它仍然被SaveDocument()的捕获捕获捕获。
这有三个原因:它很方便,因为只有一个地方可以报告错误:SaveDocument()catch块。不需要在所有子方法中重复这一点,不管怎样,这就是您想要的:一个单独的地方,为用户提供关于出错的有用诊断。
第二,每次抛出异常时都会取消保存。对于每个子方法,尝试捕捉,如果抛出异常,则进入该方法的catch块,执行离开函数,并通过SaveDocument()继续执行。如果已经出了问题,你可能会想马上停下来。
第三,所有子方法都可以假定每次调用都成功。如果调用失败,则执行将跳转到catch块,并且不会执行后续代码。这可以使代码更清晰。例如,下面是错误代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int ret = SaveFirstSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
ret = SaveSecondSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
ret = SaveThirdSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
} |
以下是例外情况下的写作方式:
1 2 3 4
| // these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection(); |
现在事情变得更清楚了。
注意,异常安全代码的编写方式可能更为复杂:如果抛出异常,则不希望泄漏任何内存。确保您了解raii、stl容器、智能指针和其他在析构函数中释放资源的对象,因为对象总是在异常之前被销毁。
- 出色的例子。是的,抓住作为高作为可能的,在逻辑单元,如不是某些"transactional运行像一个加载/保存/等。没有什么比看起来及其编码peppered具有重复性,冗余try- catch那块attempt国旗上的每一个不同的置换系稍长一些的错误与一个不怎么熟的不同的消息,当他们在现实中应该全端的同一交易或程序故障和境!!!!!!!如果一个例外-沃西occurs失败,我wager用户最想要的是什么,他们可以协会或,至少,是独自不具有对新政与10水平,关于它的消息。
- 国际海事组织(IMO)应该是公认的anwser这里。
如其他答案中所述,只有当您能够对异常进行某种明智的错误处理时,才应该捕获异常。
例如,在产生问题的问题中,发问者询问是否可以安全地忽略从整数到字符串的lexical_cast的异常。这样的演员阵容永远不会失败。如果它确实失败了,程序中就出现了严重的错误。在那种情况下,你能做些什么来恢复?最好是让程序死掉,因为它处于不可信任的状态。因此,不处理异常可能是最安全的事情。
如果您总是在可以抛出异常的方法的调用方中立即处理异常,那么异常将变得无用,您最好使用错误代码。
异常的关键是不需要在调用链中的每个方法中处理它们。
我听到的最好的建议是,您应该只在可以明智地对异常情况做些什么的时候捕获异常,而"捕获、记录和发布"并不是一个好策略(如果在库中偶尔不可避免的话)。
- 但是,在抓,日志和再次引发的异常,可以做一个好的策略。
- "keithb:我会考虑这一次的最佳战略。它的更好,如果你可以得到的日志的书写中的另一个方式。
- "keithb:它是一个"更好的比什么都不在一个图书馆的"战略"。抓,日志,恰当处理与它"可能是更好的地方。(是的,我知道这不是总是可能的。)
我同意你问题的基本方向,即在最底层处理尽可能多的异常。
现有的一些回答类似于"您不需要处理异常"。根据我的经验,这是一个不好的借口,不考虑当前开发的代码中的异常处理,使异常处理成为其他人或以后的问题。
这个问题在分布式开发中急剧增长,您可能需要调用由同事实现的方法。然后,您必须检查一个嵌套的方法调用链,以找出他/她向您抛出异常的原因,在最深的嵌套方法中,可以更容易地处理这些异常。
我的计算机科学教授曾经给我的建议是:"只有在无法用标准方法处理错误的情况下,才使用尝试和捕获块。"
作为一个例子,他告诉我们,如果一个程序在一个不可能做如下事情的地方遇到了严重的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
} |
然后你应该使用Try,Catch块。虽然您可以使用异常来处理这一点,但通常不建议这样做,因为异常在性能上是昂贵的。
- 这是一种策略,但许多人建议不要从函数返回错误代码或失败/成功状态,而是使用异常。基于异常的错误处理通常比基于错误代码的代码更容易读取。(以阿什利斯布莱恩对这个问题的回答为例)同时,要记住,许多计算机科学教授在编写真正的代码方面经验很少。
- -1@sagelika你的答案是避免例外,所以不需要尝试捕捉。
- @克里斯托弗:返回代码的另一个大缺点是,很容易忘记检查返回代码,而且在调用后不一定是处理问题的最佳位置。
- 嗯,这要看情况而定,但在很多情况下(如果人们真的不应该扔东西的话,就把他们放在一边),例外情况比返回代码要好得多,原因有很多。在大多数情况下,例外情况对性能不利的想法是一个很大的ol'[需要引用]
如果要测试每个函数的结果,请使用返回代码。
异常的目的是为了减少测试结果的频率。其思想是将异常(异常、罕见)条件从更普通的代码中分离出来。这使得普通代码更干净、更简单——但仍然能够处理这些异常情况。
在设计良好的代码中,更深的函数可能会抛出,更高的函数可能会捕获。但关键是,许多"介于两者之间"的功能将完全摆脱处理异常情况的负担。它们只需要"例外安全",这并不意味着它们必须捕获。
除上述建议外,我个人还使用了Try+Catch+Throw;原因如下:
在不同编码器的边界上,我使用try+catch+throw输入自己编写的代码,在异常被抛出给其他人编写的调用者之前,这给了我一个机会了解我的代码中发生的一些错误情况,并且这个地方离最初抛出异常的代码更近,越近,越容易找到理由。
在模块的边界,虽然不同的模块可以写我的同一个人。
学习+调试的目的,在这种情况下,我使用C++中的catch(…)和C++中的catch(ExtEx),对于C++,标准库不会抛出太多的异常,所以这种情况在C++中是很少见的。但是在C,C中常见的地方有一个巨大的库和一个成熟的异常层次结构,C库代码抛出了大量的异常,理论上我(和您)应该知道您调用的函数中的每一个异常,并且知道这些异常被抛出的原因/情况,并且知道如何优雅地处理它们(通过或捕获并处理到位)。不幸的是,在我编写一行代码之前,很难了解所有可能的异常。因此,当任何异常真正发生时,我通过记录(在产品环境中)/断言对话框(在开发环境中)来捕获所有异常,并让我的代码大声说出。通过这种方式,我逐步添加异常处理代码。我知道这会与好的建议相混淆,但实际上它对我很有用,我不知道有什么更好的方法来解决这个问题。
我想补充这个讨论,因为C++ 11,它确实有很多意义,只要每个EDCOX1 0块EDOCX1 1都例外,直到它可以/应该处理的点。这样就可以生成回溯。因此,我认为以前的观点部分过时了。
使用
std::nested_exception和
std::throw_with_nested。
在stackoverflow中,这里和这里都描述了如何实现这一点。
由于可以对任何派生的异常类执行此操作,因此可以向此类回溯添加大量信息!你也可以看看我在Github上的mwe,这里的回溯看起来像这样:
1 2 3 4
| Library API : Exception caught in function 'api_function'
Backtrace :
~ /Git /mwe -cpp -exception /src /detail /Library. cpp:17 : library_function failed
~ /Git /mwe -cpp -exception /src /detail /Library. cpp:13 : could not open file "nonexistent.txt" |
我觉得有必要再加一个答案,尽管麦克·麦特的回答很好地总结了主要观点。我是这样想的。当你有做很多事情的方法时,你是在增加复杂性,而不是增加复杂性。
换句话说,包装在try-catch中的方法有两个可能的结果。您有非异常结果和异常结果。当你处理很多方法的时候,这个指数级的爆炸会让你无法理解。
指数化,因为如果每个方法以两种不同的方式分支,那么每次调用另一个方法时,都要对前面的潜在结果数进行平方。当你调用五种方法时,你至少有256种可能的结果。将其与不在每个方法中执行try/catch进行比较,您只有一条路径可以遵循。
我就是这么看的。您可能会认为任何类型的分支都会执行相同的操作,但Try/Catch是一种特殊情况,因为应用程序的状态基本上是未定义的。
因此,简而言之,尝试/捕获使代码更难理解。
我有机会挽救几个项目,管理人员替换了整个开发团队,因为这个应用程序有太多的错误,用户厌倦了这些问题,四处乱跑。这些代码库都在应用程序级别进行了集中的错误处理,就像最热门的答案所描述的那样。如果这个答案是最佳实践,为什么它不起作用,并且允许以前的开发团队解决问题?也许有时候不起作用?上面的答案没有提到开发人员花了多长时间来解决单个问题。如果解决问题的时间是关键指标,那么使用try..catch块检测代码是一种更好的做法。
我的团队如何在不显著更改用户界面的情况下解决问题?简单地说,每个方法都被检测到try..catch阻塞,并在失败点记录所有内容,方法名称、方法参数值与错误消息、错误消息、应用程序名称、日期和版本一起连接到传递的字符串中。有了这些信息,开发人员可以对错误进行分析,以确定发生最多的异常!或错误数最多的命名空间。它还可以验证模块中发生的错误是否得到了正确处理,并且不是由多个原因引起的。
另一个好处是,开发人员可以在错误日志记录方法中设置一个断点,只需单击一个断点和"退出"调试按钮,他们就可以在故障点完全访问实际对象失败的方法中,方便地在即时窗口中找到。它使得调试非常容易,并允许将执行拖回到方法的开头,以复制问题以找到准确的行。集中异常处理是否允许开发人员在30秒内复制异常?不。
语句"一个方法只有在它能够以某种合理的方式处理异常时才应该捕获它。"这意味着开发人员可以预测或者将遇到在发布之前可能发生的每一个错误。如果这是一个顶级的,那么就不需要应用程序异常处理程序,也就没有弹性搜索和日志存储的市场。
这种方法还可以让开发人员发现和修复生产中的间歇性问题!是否要在生产中不使用调试程序进行调试?或者你更愿意打电话,从心烦意乱的用户那里收到电子邮件?这使您能够在其他人知道之前解决问题,而无需发送电子邮件、即时消息或在支持方面松懈,因为解决问题所需的一切都在那里。95%的问题不需要复制。
要正常工作,需要将它与集中式日志记录结合起来,后者可以捕获命名空间/模块、类名、方法、输入和错误消息,并存储在数据库中,这样就可以对其进行聚合,以突出显示哪个方法失败得最多,从而可以首先修复它。
有时开发人员选择从catch块向上抛出异常,但这种方法比不抛出的正常代码慢100倍。最好使用日志记录进行捕获和释放。
这项技术被用于快速稳定一个应用程序,该应用程序在一家财富500强公司中每小时都会失败,这家公司由12个开发者开发,历时两年。使用这3000个不同的例外情况在4个月内被识别、修复、测试和部署。平均每15分钟修复一次,持续4个月。
我同意输入所有需要插入代码的东西并不是一件有趣的事情,我更喜欢不看重复的代码,但是从长远来看,为每个方法添加4行代码是值得的。
- 把每一个街区都包起来似乎太过分了。它会很快使代码膨胀,并且很难阅读。从更高级别的异常记录stacktrace可以显示问题发生的位置,并且与错误本身结合在一起通常是足够的信息。我很好奇你在哪里发现这还不够。这样我就能获得别人的经验。
- "异常比正常代码慢100到1000倍,永远不应该重新执行"——在大多数现代编译器和硬件上,这种说法是不正确的。
- 这看起来有点过分杀伤力,需要输入一些内容,但这是对异常进行分析的唯一方法,以便首先找到并修复最大的错误,包括生产中的间歇性错误。如果需要,catch块会处理特定的错误,并有一行记录的代码。
- 不,例外很慢。另一种选择是返回代码、对象或变量。查看此堆栈溢出日志…"例外情况至少比返回代码"stackoverflow.com/questions/891217/…慢30000倍。
您不需要将代码的每个部分都隐藏在try-catch中。try-catch块的主要用途是错误处理和程序中的错误/异常。try-catch的一些用法-
您可以在希望处理异常的地方使用这个块,或者简单地说,编写的代码块可能引发异常。
如果您想在使用后立即处理对象,可以使用try-catch块。
- "如果要在对象使用后立即处理它们,可以使用Try-Catch块。"是否要提升raii/最小对象生存期?如果是这样,那么,try/catch与之完全分离/正交。如果你想在一个较小的范围内处理物体,你可以打开一个新的{ Block likeThis; /* <- that object is destroyed here -> */ }—当然,除非你确实需要catch任何东西,否则不需要用try/catch来包装这个东西。
- #2-处理异常中的对象(手工创建的)对我来说似乎很奇怪,这在某些语言中无疑是有用的,但通常情况下,您在"try/except块内"尝试/最终完成此操作,而不是在except块本身中进行此操作-因为对象本身可能是导致异常的原因,并且我们会导致另一个例外,并可能导致崩溃。