捕获和抛出异常;为什么这被认为是反模式

Catching and Throwing exceptions; Why is this considered an anti-pattern

public void DeployCourse(Course course, Client client)
{
    if (course == null) throw new ArgumentNullException("Course cannot be null");       
    if (client == null) throw new ArgumentNullException("Client cannot be null");

    try
    {
        _ftp.Transfer(client.Server.IPAddress, course.PackageUrl, course.CourseName);
    }
    catch (Exception e)
    {
        var newException = new Exception(String.Format(
            "Error deploying Course: {0} to Client: {1}. See inner exception for more details", 
            course.CourseName, client.Name), e);
        throw newException;
    }
}

我从来没有真正理解 "good" 异常处理的构成。快速 google 搜索表明,人们几乎一致认为在调用堆栈深处捕获和重新抛出异常是不好的。上面,我有一些我正在编写的代码的示例。这在典型的调用堆栈中非常低。我这样做是因为,如果没有这段代码,就很难准确找到部署失败的课程。我的问题是,如果我在许多(如果不是全部)方法中做了类似的事情,为异常添加更多上下文,为什么这会被视为反模式?

谢谢!

这个问题有点含糊,所以让我们试着把它弄清楚一点。

Is the pattern of catching an exception, wrapping it up in a different exception, and throwing the new exception to the caller, a good pattern?

是的,非常好。像任何模式一样,它有利有弊。

What are the pros?

当抛出的异常在逻辑上与调用者期望方法调用执行的操作相关时,效果最佳。如果 "operation Foo can throw a FooFailedException" 是记录在案的合同的一部分,那么开发人员就知道他们需要捕捉 FooFailedException 并且 - 这是关键点 - only FooFailedException.

如果您始终如一地应用它,那么您可以更改方法的实现细节,而不必担心会破坏调用者。

例如,如果您需要调用方捕获与 FTP 站点故障相关的异常,然后您更改您的实现以支持其他一些抛出不同异常的协议,那么调用者也必须进行更改以捕获新的异常(或者他们必须捕获所有内容)。使用这种模式,无论实现细节如何,调用者都可以只捕获一个异常。

这还允许调用者根据他们尝试执行的操作获取诊断信息,而不是根据失败的实现细节。

What are some of the cons?

  1. 如果更改较晚,则更改抛出的异常可能是重大更改。

  2. 调用者可能希望捕获更具体的底层异常。

  3. 如果底层异常被抛出因为你的代码有一个 bug 并且本身调用了错误的东西那么你隐藏了你的 bug 并传递了它的后果转到来电者。

  4. 有些例外如"the network is down right now, try again later",有些例外如"everything is terrible and you really should be shutting this process down"。通过捕获所有内容并将其包装起来,您会更难知道异常是否可恢复。

  5. 我们有一份被黑客破坏的证书列表;我们将名单保存在互联网上。如果证书在已撤销证书列表中,我们不希望使用该证书。快,找到缺陷:

    bool revoked = false;
    try
    {
        CheckTheRevocationList(out revoked);
    }
    catch(Exception ex)
    {
        new CryptoException("revocation list could not be checked", ex);
    }
    if (!revoked) UseTheCertificate();
    

看到缺陷了吗?那是一个安全漏洞。在 999 种模式的正确用法的大海捞针中找到那根针对人类来说是非常困难的。 (这不是理论上的;我通过编写一个查找它的静态分析器在实际的实时代码中发现了这个缺陷的一个版本。)

Where can I read more about exception handling patterns and practices?

我的文章在这里:

http://ericlippert.com/category/exception-handling/

SO 上经常引用您可能应该开始的那个:

http://ericlippert.com/2008/09/10/vexing-exceptions/