我一直相信,如果一个方法可以抛出异常,那么不使用有意义的try块来保护这个调用就是鲁莽的。
我刚刚发布了‘你应该总是包装调用,可以抛出try, catch块。,结果被告知这是一个“非常糟糕的建议”——我想知道为什么。
我一直相信,如果一个方法可以抛出异常,那么不使用有意义的try块来保护这个调用就是鲁莽的。
我刚刚发布了‘你应该总是包装调用,可以抛出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容器、智能指针和其他在析构函数中释放资源的对象,因为对象总是在异常之前被析构。
其他回答
尽管Mike Wheat的回答很好地总结了要点,但我还是觉得有必要再补充一个答案。我是这样想的。当你有方法做很多事情时,你是在增加复杂性,而不是增加它。
换句话说,封装在try catch中的方法有两种可能的结果。有非异常结果和异常结果。当你处理很多方法的时候这个指数级的爆炸超出了你的理解。
因为如果每个方法都以两种不同的方式分支,那么每次调用另一个方法时,你都是在对之前的潜在结果数进行平方。当你调用了5个方法时,你至少有256个可能的结果。与此相比,在每个方法中都不执行try/catch,您只有一条路径可以遵循。
我基本上就是这么看的。您可能会认为任何类型的分支都做同样的事情,但try/catch是一个特殊情况,因为应用程序的状态基本上是未定义的。
简而言之,try/catch使代码更难理解。
正如在其他回答中所述,只有在可以对异常进行某种合理的错误处理时才应该捕获异常。
例如,在生成您的问题的问题中,提问者询问忽略从整数到字符串的lexical_cast异常是否安全。这样的阵容永远不会失败。如果它失败了,说明程序中出现了严重的错误。在这种情况下,你能做些什么来恢复呢?最好的方法可能是让程序死亡,因为它处于不可信任的状态。因此,不处理异常可能是最安全的做法。
如果您想测试每个函数的结果,请使用返回码。
exception的目的是为了降低测试结果的频率。其思想是将异常(不寻常的,罕见的)条件从更普通的代码中分离出来。这使得普通代码更简洁,但仍然能够处理那些异常情况。
在设计良好的代码中,较深的函数可能会抛出,较高级的函数可能会捕获。但关键是,许多“介于两者之间”的功能将完全摆脱处理异常情况的负担。它们只需要是“异常安全的”,这并不意味着它们必须捕获。
我的计算机科学教授曾经给我的建议是:“只有在使用标准方法无法处理错误时,才使用Try and Catch块。”
作为一个例子,他告诉我们,如果一个程序在一个地方遇到了一些严重的问题,而不可能做这样的事情:
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
然后你应该使用try, catch块。虽然您可以使用异常来处理这个问题,但通常不建议这样做,因为异常会消耗大量性能。
我听到过的最好的建议是,您应该只在可以对异常条件采取措施的情况下捕获异常,而“捕获、记录和释放”并不是一个好策略(如果在库中偶尔不可避免的话)。