我一直认为,如果一个方法可以引发异常,那么不使用有意义的try块来保护此调用是鲁re的。
我刚刚发布了“ 您应该总是包装可能会引发尝试,捕获块的调用。” 这个问题,并被告知这是“非常糟糕的建议”-我想了解原因。
我一直认为,如果一个方法可以引发异常,那么不使用有意义的try块来保护此调用是鲁re的。
我刚刚发布了“ 您应该总是包装可能会引发尝试,捕获块的调用。” 这个问题,并被告知这是“非常糟糕的建议”-我想了解原因。
Answers:
方法只能以某种合理的方式处理它时才捕获异常。
否则,将其传递下去,希望调用堆栈上方的方法可以理解它。
正如其他人指出的那样,优良作法是在调用堆栈的最高级别上使用未处理的异常处理程序(带有日志记录),以确保记录所有致命错误。
try
块存在成本(就生成的代码而言)。Scott Meyers的“更有效的C ++”中有很好的讨论。
try
,任何现代C编译器中的块都是免费的,该信息标明了尼克的名字。我也不同意使用顶级异常处理程序,因为您会丢失位置信息(指令失败的实际位置)。
terminate
) 。这更像是一种安全机制。另外,try/catch
在没有任何例外的情况下,或多或少都可以免费使用。当有一个传播时,它确实会在每次抛出和捕获时都消耗时间,因此try/catch
,只有一次重新抛出并不是没有代价的。
正如Mitch 和 其他人所说,您不应捕获不打算以某种方式处理的异常。在设计应用程序时,应考虑应用程序将如何系统地处理异常。这通常会导致基于抽象的错误处理层-例如,您处理数据访问代码中的所有与SQL相关的错误,以使与域对象进行交互的应用程序部分不会暴露于以下事实:是某个地方的数据库。
除了“随处捕获”气味之外,您还绝对要避免一些相关的代码气味。
“捕获,日志,重新抛出”:如果要基于作用域的日志记录,则编写一个类,该类在堆栈由于异常而展开时会在其析构函数中发出log语句(ala std::uncaught_exception()
)。所有你需要做的就是申报的范围记录实例,你有兴趣和,瞧,你已经登录并没有不必要try
/ catch
逻辑。
“捕获,抛出翻译”:这通常指向一个抽象问题。除非您要实现一种联合解决方案,在该解决方案中,您将几个特定的异常转换为一个更通用的异常,否则您可能会有不必要的抽象层…… 并且不要说“我明天可能需要它”。
“捕捉,清理,扔掉”:这是我的宠儿之一。如果您看到很多,则应应用“ 资源获取即初始化”技术,并将清除部分放置在管理员对象实例的析构函数中。
我认为,用try
/ catch
块填充的代码是代码查看和重构的良好目标。它表明,要么对异常处理的理解不充分,要么代码已成为难题,并且亟需进行重构。
Logger
类似于log4j.Logger
在每个日志行中包含线程ID 的类,并在异常活动时在析构函数中发出警告。
您不需要用try-catches 覆盖每个块,因为try-catch仍然可以捕获在调用堆栈之后的函数中引发的未处理异常。因此,除了让每个函数都具有try-catch之外,您还可以在应用程序的顶级逻辑中拥有一个。例如,可能有一个SaveDocument()
顶层例程,该例程调用许多调用其他方法的方法,等等。这些子方法不需要它们自己的try-catches,因为如果它们抛出,它仍然会被SaveDocument()
catch捕获。
这样做有以下三个原因:方便,因为您只有一个地方可以报告错误:SaveDocument()
catch块。无需在所有子方法中重复进行此操作,而这正是您想要的:在一个位置为用户提供有关发生错误的有用诊断信息。
第二,只要引发异常,保存就会被取消。随着每个子方法试捕,如果抛出一个异常,你在该方法的catch块,执行叶功能,并进行通过SaveDocument()
。如果发生了问题,您可能想就此停下来。
第三,您所有的子方法都可以假定每个调用都成功。如果调用失败,执行将跳转到catch块,并且以后的代码将永远不会执行。这可以使您的代码更整洁。例如,这里有错误代码:
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;
}
这是例外情况的写法:
// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();
现在更清楚了发生了什么。
请注意,异常安全代码可以用其他方式来编写,比较棘手:如果抛出异常,您就不会泄漏任何内存。确保了解RAII,STL容器,智能指针和其他将其资源释放到析构函数中的对象,因为对象总是在异常之前被破坏。
try
-这些catch
块试图用略有不同的消息来标记某个错误的每个略有不同的排列,而实际上它们都应该以相同的结尾:事务或程序失败并退出!如果发生异常异常故障,我打赌大多数用户只想挽救自己可以解决的问题,或者至少可以独自一人解决而不用处理有关该问题的10个级别的消息。
赫伯萨特在这里写了这个问题。确实值得一读。
预告片:
“编写异常安全代码从根本上讲就是在正确的位置编写'try'和'catch'。” 讨论。
坦率地说,该声明反映了对异常安全性的根本误解。异常只是错误报告的另一种形式,我们当然知道编写错误安全代码并不只是检查返回代码和处理错误情况的地方。
实际上,事实证明,异常安全性很少是关于编写“ try”和“ catch”的,而且越少越好。同样,永远不要忘记异常安全性会影响代码的设计。这绝不是事后的想法,可以通过添加一些额外的渔获量声明加以改进,就像调味一样。
我听到的最好建议是,仅应在可以合理地对异常情况进行处理的地方捕获异常,并且“捕获,记录和释放”不是一个好的策略(如果偶尔在库中不可避免)。
我的计算机科学教授曾经给我的建议是:“仅在不可能使用标准方法处理错误时,才使用Try and Catch块。”
例如,他告诉我们,如果某个程序在无法执行以下操作的地方遇到了严重问题:
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
然后,您应该使用try,catch块。尽管您可以使用异常来处理此问题,但通常不建议这样做,因为在性能上,异常是很昂贵的。
我想补充一下这一讨论,因为自C ++ 11以来,它确实很有意义,只要每个catch
块rethrow
都是异常,直到可以/应该处理该点为止。这样可以生成回溯。因此,我认为先前的观点在某种程度上已经过时了。
std::nested_exception
和std::throw_with_nested
它在此处以及此处如何实现的StackOverflow上进行了描述。
由于您可以使用任何派生的异常类执行此操作,因此可以向此类回溯中添加很多信息!您也可以在GitHub上查看我的MWE,回溯显示如下:
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块检测代码是一种更好的做法。
我的团队如何在不显着更改UI的情况下解决问题?很简单,每个方法都使用try..catch进行阻止,并在故障点记录所有内容,包括方法名称,方法参数值连接到字符串中,以及错误消息,错误消息,应用程序名称,日期,和版本。利用此信息,开发人员可以对错误进行分析,以找出最常发生的异常!或错误次数最多的名称空间。它还可以验证是否正确处理了模块中发生的错误,并且该错误不是由多种原因引起的。
这样做的另一个好处是,开发人员可以在错误记录方法中设置一个断点,并且只需单击一个中断点并单击“逐步退出”调试按钮,就可以在该方法中失败,并且可以完全访问实际故障点的对象,可在立即窗口中方便地使用。它使调试变得非常容易,并允许将执行拖回方法的开头以复制问题以找到确切的行。集中式异常处理是否允许开发人员在30秒内复制异常?没有。
语句“方法只能以某种明智的方式处理它时才捕获异常”。这意味着开发人员可以预测或将遇到发行前可能发生的所有错误。如果确实如此,那么就不需要应用程序异常处理程序,并且Elastic Search和logstash不会有市场。
这种方法还使开发人员可以发现并解决生产中的间歇性问题!您想在生产环境中不使用调试器进行调试吗?还是您更愿意接听电话并从烦恼的用户那里收到电子邮件?这使您可以在其他人不知道的情况下解决问题,而无需通过电子邮件,IM或Slack寻求支持,因为解决问题所需的一切就在那里。95%的问题都无需复制。
为了使其正常工作,需要与集中式日志记录结合使用,该日志记录可以捕获名称空间/模块,类名,方法,输入和错误消息并存储在数据库中,以便可以汇总以突出显示哪种方法失败最多,从而可以首先修复。
有时,开发人员选择从catch块向堆栈中抛出异常,但是这种方法比不抛出异常的普通代码慢100倍。最好使用日志记录来捕获和释放。
这项技术用于快速稳定一个应用程序,该应用程序在由12个开发人员在两年内开发的财富500强公司中,大多数用户每小时都会失败。使用这3000个不同的异常,可以在4个月内识别,修复,测试和部署。平均四个月平均每15分钟修复一次。
我同意键入检测代码所需的所有内容并不是一件好事,并且我宁愿不要查看重复的代码,但从长远来看,为每个方法添加4行代码是值得的。
除了上述建议外,我个人还使用try + catch + throw;出于以下原因:
尽管迈克·惠特的答案很好地总结了要点,但我还是不得不添加另一个答案。我是这样想的。当您拥有执行多项任务的方法时,您正在增加复杂性,而不是相加。
换句话说,包装在try catch中的方法有两个可能的结果。您有非异常结果和异常结果。当您处理许多方法时,这将成倍地激增,超出了理解范围。
指数级地表示,因为如果每种方法以两种不同的方式分支,那么每次调用另一种方法时,您的结果就变成了以前的数量。到您调用五种方法时,您至少可以获得256种可能的结果。与之相比,不要在每种方法中都进行try / catch,则只有一条路径可以遵循。
基本上这就是我的看法。您可能会争辩说任何类型的分支都可以做相同的事情,但是try / catches是一种特例,因为应用程序的状态基本上变得不确定。
简而言之,尝试/捕获使代码难于理解。
您无需掩盖其中的代码的每个部分try-catch
。该try-catch
块的主要用途是错误处理,并在程序中包含错误/异常。的一些使用try-catch
-
try-catch
块。try
/ catch
与此完全分离/正交。如果要在较小的范围内放置对象,则可以打开一个新对象{ Block likeThis; /* <- that object is destroyed here -> */ }
-无需将其包装在try
/中,catch
除非您确实需要catch
任何东西。