我一直相信,如果一个方法可以抛出异常,那么不使用有意义的try块来保护这个调用就是鲁莽的。

我刚刚发布了‘你应该总是包装调用,可以抛出try, catch块。,结果被告知这是一个“非常糟糕的建议”——我想知道为什么。


当前回答

尽管Mike Wheat的回答很好地总结了要点,但我还是觉得有必要再补充一个答案。我是这样想的。当你有方法做很多事情时,你是在增加复杂性,而不是增加它。

换句话说,封装在try catch中的方法有两种可能的结果。有非异常结果和异常结果。当你处理很多方法的时候这个指数级的爆炸超出了你的理解。

因为如果每个方法都以两种不同的方式分支,那么每次调用另一个方法时,你都是在对之前的潜在结果数进行平方。当你调用了5个方法时,你至少有256个可能的结果。与此相比,在每个方法中都不执行try/catch,您只有一条路径可以遵循。

我基本上就是这么看的。您可能会认为任何类型的分支都做同样的事情,但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块。虽然您可以使用异常来处理这个问题,但通常不建议这样做,因为异常会消耗大量性能。

如果总是在可能抛出异常的方法的调用者中立即处理异常,那么异常就变得毫无用处,最好使用错误代码。

异常的全部意义在于,不需要在调用链中的每个方法中处理它们。

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容器、智能指针和其他在析构函数中释放资源的对象,因为对象总是在异常之前被析构。