【问题标题】:Catching and Throwing exceptions; Why is this considered an anti-pattern捕获和抛出异常;为什么这被认为是反模式
【发布时间】:2015-06-23 06:25:57
【问题描述】:
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;
    }
}

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

谢谢!

【问题讨论】:

  • 不,不是这样。编写 catch (Exception ex) { throw ex; 的程序员} 犯下那个罪。太常见了。
  • 我相信真正的反模式是抛出异常,然后将其作为特殊的返回码而不是真正的异常情况来捕获。包装异常以提供更多详细信息是很好的 IMO。
  • 我同意这不是一个糟糕的模式。您确保抛出的异常在逻辑上连接到被调用的方法,这对于您的 API 用户来说是一个很好的属性。而且您保留旧的异常以帮助诊断。当然有办法滥用这种模式,但我认为这里显示的代码是合理的。
  • 现在,您的代码中有一个错误。传递给 ArgumentNullException 的 ctor 的字符串应该是违规参数的名称,而不是信息性消息。如果您想要一条消息,那么它应该是(名称,消息)。 注意 ArgumentException 是 (message, name),ArgumentNullException 是 (name, message) THANKS MICROSOFT.
  • 您还应该抛出比“新异常”更好的东西。制作一个新的 CourseDeploymentFailureException 或类似的。

标签: c# exception-handling


【解决方案1】:

这个问题有点含糊,所以让我们试着梳理一下。

捕获异常,将其包装在不同的异常中,然后将新的异常抛出给调用者的模式是一个好的模式吗?

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

有什么优点?

当抛出的异常与调用者期望方法调用执行的操作在逻辑上相关时,它的效果最好。如果“操作Foo 可以抛出FooFailedException”是记录合同的一部分,那么开发人员就知道他们需要捕获FooFailedException 并且 -- 这是关键点 -- @987654326 @。

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

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

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

有什么缺点?

  1. 如果更改较晚,则更改引发的异常可能是一项重大更改。

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

  3. 如果引发底层异常是因为您的代码存在错误并且本身调用了错误,那么您就是在隐藏错误并将其后果传递给调用者。 p>

  4. 有些异常例如“网络现在已关闭,请稍后再试”,有些异常例如“一切都很糟糕,您真的应该关闭此进程”。通过捕获所有内容并将其包装起来,您就更难知道异常是否可恢复。

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

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

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

我在哪里可以阅读有关异常处理模式和实践的更多信息?

我的文章在这里:

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

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

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

【讨论】:

  • 缺陷是缺少“throw”关键字还是您打错字?
  • @Tergiver:丢失的 throw 是缺陷的主要原因,但更一般地说,这是“不安全模式失败”类缺陷。如果 "revoked" 的默认值是 true 而不是 false,那么代码可能会在缺少 throw 的情况下表现不正确,但它会是安全的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-16
  • 2010-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-04
相关资源
最近更新 更多