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

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


当前回答

As Mitch and others stated, you shouldn't catch an exception that you do not plan on handling in some way. You should consider how the application is going to systematically handle exceptions when you are designing it. This usually leads to having layers of error handling based on the abstractions - for example, you handle all SQL-related errors in your data access code so that the part of the application that is interacting with domain objects is not exposed to the fact that there is a DB under the hood somewhere.

除了“在任何地方捕获所有东西”的味道之外,还有一些相关的代码味道是您一定要避免的。

"catch, log, rethrow": if you want scoped based logging, then write a class that emits a log statement in its destructor when the stack is unrolling due to an exception (ala std::uncaught_exception()). All that you need to do is declare a logging instance in the scope that you are interested in and, voila, you have logging and no unnecessary try/catch logic. "catch, throw translated": this usually points to an abstraction problem. Unless you are implementing a federated solution where you are translating several specific exceptions into one more generic one, you probably have an unnecessary layer of abstraction... and don't say that "I might need it tomorrow". "catch, cleanup, rethrow": this is one of my pet-peeves. If you see a lot of this, then you should apply Resource Acquisition is Initialization techniques and place the cleanup portion in the destructor of a janitor object instance.

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