我一直相信,如果一个方法可以抛出异常,那么不使用有意义的try块来保护这个调用就是鲁莽的。
我刚刚发布了‘你应该总是包装调用,可以抛出try, catch块。,结果被告知这是一个“非常糟糕的建议”——我想知道为什么。
我一直相信,如果一个方法可以抛出异常,那么不使用有意义的try块来保护这个调用就是鲁莽的。
我刚刚发布了‘你应该总是包装调用,可以抛出try, catch块。,结果被告知这是一个“非常糟糕的建议”——我想知道为什么。
当前回答
I was given the "opportunity" to salvage several projects and executives replaced the entire dev team because the app had too many errors and the users were tired of the problems and run-around. These code bases all had centralized error handling at the app level like the top voted answer describes. If that answer is the best practice why didn't it work and allow the previous dev team to resolve issues? Perhaps sometimes it doesn't work? The answers above don't mention how long devs spend fixing single issues. If time to resolve issues is the key metric, instrumenting code with try..catch blocks is a better practice.
How did my team fix the problems without significantly changing the UI? Simple, every method was instrumented with try..catch blocked and everything was logged at the point of failure with the method name, method parameters values concatenated into a string passed in along with the error message, the error message, app name, date, and version. With this information developers can run analytics on the errors to identify the exception that occurs the most! Or the namespace with the highest number of errors. It can also validate that an error that occurs in a module is properly handled and not caused by multiple reasons.
Another pro benefit of this is developers can set one break-point in the error logging method and with one break-point and a single click of the "step out" debug button, they are in the method that failed with full access to the actual objects at the point of failure, conveniently available in the immediate window. It makes it very easy to debug and allows dragging execution back to the start of the method to duplicate the problem to find the exact line. Does centralized exception handling allow a developer to replicate an exception in 30 seconds? No.
语句“方法只有在能够以某种合理的方式处理异常时才应该捕获异常。”这意味着开发人员可以预测或将遇到在发布之前可能发生的每一个错误。如果这是真的,那么应用程序异常处理程序就不需要了,Elastic Search和logstash也就没有市场了。
这种方法还可以让开发人员发现并修复生产中的间歇性问题!是否希望在生产环境中不使用调试器进行调试?或者你宁愿接那些心烦意乱的用户的电话和邮件?这可以让你在其他人知道之前解决问题,而不必通过电子邮件、即时通讯或Slack寻求支持,因为解决问题所需的一切都在那里。95%的问题永远不需要被复制。
为了正常工作,它需要与集中式日志记录相结合,该日志记录可以捕获名称空间/模块、类名、方法、输入和错误消息并存储在数据库中,以便可以聚合它以突出显示哪个方法失败最多,以便首先修复它。
有时候开发人员会选择从catch块向堆栈抛出异常,但这种方法比不抛出异常的普通代码慢100倍。优先使用日志记录进行捕获和释放。
在一家财富500强公司中,该技术被用于快速稳定一款每小时都会出现故障的应用,该应用是由12名开发者历时2年开发的。使用这3000个不同的异常在4个月内被识别、修复、测试和部署。这平均每15分钟修复一次,持续4个月。
我同意,输入所有需要的代码并不有趣,我更喜欢不看重复的代码,但从长远来看,为每个方法添加4行代码是值得的。
其他回答
我的计算机科学教授曾经给我的建议是:“只有在使用标准方法无法处理错误时,才使用Try and Catch块。”
作为一个例子,他告诉我们,如果一个程序在一个地方遇到了一些严重的问题,而不可能做这样的事情:
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
然后你应该使用try, catch块。虽然您可以使用异常来处理这个问题,但通常不建议这样做,因为异常会消耗大量性能。
You don't need to cover every block with try-catches because a try-catch can still catch unhandled exceptions thrown in functions further down the call stack. So rather than have every function have a try-catch, you can have one at the top level logic of your application. For example, there might be a SaveDocument() top-level routine, which calls many methods which call other methods etc. These sub-methods don't need their own try-catches, because if they throw, it's still caught by SaveDocument()'s catch.
这样做很好,有三个原因:它很方便,因为只有一个地方可以报告错误:SaveDocument()捕获块。没有必要在所有子方法中重复这一点,而且这正是您想要的:在一个单一的位置为用户提供关于出错的有用诊断。
第二,每当抛出异常时,保存将被取消。对于每个尝试捕获子方法,如果抛出异常,则进入该方法的捕获块,执行离开函数,并通过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容器、智能指针和其他在析构函数中释放资源的对象,因为对象总是在异常之前被析构。
除了上面的建议,我个人使用尝试+抓+扔的方法;原因如下:
At boundary of different coder, I use try + catch + throw in the code written by myself, before the exception being thrown to the caller which is written by others, this gives me a chance to know some error condition occured in my code, and this place is much closer to the code which initially throw the exception, the closer, the easier to find the reason. At the boundary of modules, although different module may be written my same person. Learning + Debug purpose, in this case I use catch(...) in C++ and catch(Exception ex) in C#, for C++, the standard library does not throw too many exception, so this case is rare in C++. But common place in C#, C# has a huge library and an mature exception hierarchy, the C# library code throw tons of exception, in theory I(and you) should know every exceptions from the function you called, and know the reason/case why these exception being thrown, and know how to handle them(pass by or catch and handle it in-place)gracefully. Unfortunately in reality it's very hard to know everything about the potential exceptions before I write one line of code. So I catch all and let my code speak aloud by logging(in product environment)/assert dialog(in development environment) when any exception really occurs. By this way I add exception handling code progressively. I know it conflit with good advice but in reality it works for me and I don't know any better way for this problem.
方法应该只在能够以某种合理的方式处理异常时才捕获异常。
否则,将其向上传递,希望调用堆栈中更高位置的方法能够理解它。
正如其他人所注意到的,在调用堆栈的最高级别上使用一个未处理的异常处理程序(带有日志记录)是一种良好的实践,以确保记录任何致命错误。
如果总是在可能抛出异常的方法的调用者中立即处理异常,那么异常就变得毫无用处,最好使用错误代码。
异常的全部意义在于,不需要在调用链中的每个方法中处理它们。