【问题标题】:Good practices when handling Exceptions in C#在 C# 中处理异常时的良好做法
【发布时间】:2011-05-06 21:52:01
【问题描述】:

我在 The Pragmatic Programmer 和其他一些文章(包括 Joel Spolsky 的一篇文章)中读到,您应该只在 异常 情况下抛出异常。否则,你应该返回一个错误。

有时这是可能的(例如返回-1-0positive number),但在其他情况下这是不可能的。我的意思是,如果你要返回一个类,你总是可以返回null,但我认为这个想法是返回一些东西来提醒调用者发生了什么。

如果我总是返回null,我认为说:如果这个方法返回null,可能是因为A、B、C、D或E

那么,这究竟是如何在 C# 中实现的呢?

编辑:
在我发布这个问题几个小时后,我在这里看到了另一个问题,问题本身是关于发布的代码是否是一种好的做法。

我知道这是我在这里要求的另一种方式。这是链接:

Generic property disadvantages?

【问题讨论】:

  • 问题是假设每个消费者都会做他们应该做的返回码检查是一厢情愿的想法。这意味着他们不会检测到您的方法何时失败。异常使 API 非常 更易于使用。当一个方法未能完成它被调用的任务时,总是抛出它们。重点是从不将它们用于流量控制。
  • @CodyGray 这就是我一直在做的事情,我完全同意你的看法,但是当我在同一个地方的两个不同(和好)的地方读到同样的东西时,我忍不住要问日:实用程序员和 Joel On Software 博客。
  • 很公平。我会发布一个更全面的答案,但已经有一个被接受了,看起来投票率最高的答案很好地涵盖了它。异常是一个非常容易被误解的话题,你会看到很多熟悉它们如何在 C++ 中工作的程序员错误地不鼓励在 C# 中使用它们。 .NET Framework Design Guidelines 是一个很好的资源。
  • @CodyGray 还有其他关于异常是一个非常被误解的话题的参考资料吗?
  • 很难从有效建议中过滤错误信息。这使得在线资源非常棘手。 FDG 是我所知道的最好的参考。您还会发现许多将 C++ 异常知识应用到 C# 的程序员,或者从务实的角度来说,说某些东西是必要的,只是因为他们不知道替代方案。我经常争辩说,仅仅为了记录异常而捕获和重新抛出异常几乎总是错误的想法。它冒犯了那些认为我没有“现实世界经验”的人。像AppDomain.UnhandledException 这样的事件总是更好的选择。

标签: c# exception exception-handling


【解决方案1】:

关于何时抛出异常的更好的规则是:

当您的方法无法按照其名称执行的操作时抛出异常。

空值可用于表示您请求的内容不存在。不应为任何其他错误情况返回它。

“仅在异常情况下抛出异常”的规则对 IMO 没有帮助,因为它没有给您提供基准来指示什么是异常的,什么不是。这就像在说“只吃可食用的食物”。

【讨论】:

  • 我真的很喜欢这个答案,因为它提供了最不模糊的描述。
  • 对此我要补充一点,如果调用 TryXXX 的方法由于调用者可能预期的原因而无法执行 XXX,它会尝试执行 XXX(这意味着它按照其名称执行,因此应该不抛出异常);如果失败是调用者可能没有准备好,那么抛出异常可能会很好,因为认为某些东西阻止了它给 XXX 一个“好的尝试”。
【解决方案2】:

通过使用数字,您直接与 Microsoft 建议的例外情况相矛盾。我发现可以揭开整个主题的神秘面纱的最佳信息来源是Jeffrey Richter's CLR via C# 3。此外,.NET Framework Guidelines 这本书的异常材料也值得一读。

To quote MSDN:

不返回错误代码。异常是在框架中报告错误的主要方式。

您可以采用的一种解决方案是out 参数,它会得到结果。然后,您的方法会返回一个 bool,就像您在框架中遇到的很多 TryParse 方法一样。

【讨论】:

    【解决方案3】:

    要考虑的一个例子是int.TryParse。它使用out 参数作为解析值,并使用bool 返回值来指示成功或失败。如果情况合适,bool 可以替换为枚举或更复杂的对象。 (例如,数据验证可能会以真正保证失败集合的方式失败等)

    .NET 4 的替代方案是 Tuple... 所以 int.TryParse 可以是:

    public static Tuple<int, bool> TryParse(string text)
    

    下一个可能性是有一个对象来封装整个结果,如果合适的话,包括故障模式。例如,您可以向Task&lt;T&gt; 询问其结果、状态以及失败时的异常。

    所有这一切只有在它不是真正发生的错误时才是合适的,因此。它并不表示错误,只是类似于错误的用户输入。我真的不喜欢返回错误代码 - 异常在 .NET 中更为惯用。

    【讨论】:

    • Ack...我想我不想花太多时间处理一个散布着元组的公共 API。
    • @Jack - 如果语言支持元组扩展 (EX: F#),这种 API 会很好用。
    • @ChaosPandion:是的,在语言支持下肯定会更好。在这一点上,我个人认为它比out 参数更可取。
    • 我怎么知道上面例子中的 int 和 bool 代表什么?我想在这种情况下有点明显,但任何更复杂的事情都可能令人讨厌。不过,我也不喜欢 out param 的东西。
    • @Jack:当一个是返回值而一个是输出参数时,你怎么知道它们代表什么?确实,使用Tuple 是组合多个返回值的一种非常特别的方式,但我相信在实践中它会非常清楚,特别是如果语言支持允许您编写var (value, ok) = int.Parse(text); 来声明两个变量,value(一个int)和ok(一个bool)。
    【解决方案4】:

    Microsoft 记录了 .NET 中错误处理的最佳实践

    http://msdn.microsoft.com/en-us/library/8ey5ey87(VS.71).aspx

    我建议您遵循这些准则,因为它是 .NET 的标准,并且您与其他 .NET 开发人员的冲突要少得多。

    (请注意,我意识到我发布到了一个较旧的链接,但建议仍然存在。)

    我也意识到不同平台对正确错误处理的看法各不相同。这可能是一个主观问题,但我会坚持我上面所说的 - 在 .NET 中开发时遵循 .NET 指南。

    【讨论】:

      【解决方案5】:

      您可能需要考虑遵循 TryXXX 模式,并为客户端考虑几个简单的重载。

      // Exception
      public void Connect(Options o); 
      
      // Error Code
      public bool TryConnect(Options o, out Error e); 
      

      【讨论】:

        【解决方案6】:

        乔尔·斯波尔斯基错了。通过错误/返回码返回状态意味着你不能相信任何方法调用的结果——返回的值必须经过测试和处理。

        这意味着每个返回此类值的方法调用都会引入至少一个选择点(或更多,具体取决于返回值的域),从而通过代码引入更多可能的执行路径。所有这些都必须经过测试。

        那么,这段代码假设 Method1() 和 Method2() 的约定要么成功要么抛出异常,有 1 个可能流过它。:

        foo.Method(...) ;
        bar.Method(...) ;
        

        如果这些方法通过返回码指示状态,它会很快变得非常混乱。只返回一个二进制值:

        bool fooSuccess = foo.Method(...);
        if ( fooSuccess )
        {
          bool barSuccess = bar.Method(...);
          if ( barSuccess )
          {
            // The normal course of events -- do the usual thing
          }
          else
          {
            // deal with bar.Method() failure
          }
        }
        else // foo.Method() failed
        {
          // deal with foo.Method() failure
        }
        

        返回状态码而不是抛出异常

        • 使测试复杂化
        • 复杂的代码理解
        • 几乎肯定会引入错误,因为开发人员不会捕获和测试所有可能的情况(毕竟,您实际看到 I/O 错误发生的频率是多少?)。

        调用者应该在调用方法之前检查以确保一切正常(例如,检查文件是否存在。如果文件不存在,请不要尝试打开它)。

        执行你的方法的契约:

        • 先决条件。调用者保证在方法调用之前这些都是真的。
        • 后置条件。被调用者保证这些在方法调用之后是真实的。
        • 不变条件。被调用者保证这些条件始终为真。

        如果违反合同,则抛出异常。 如果发生任何意外情况,则抛出异常。

        你的代码应该是一个严格的纪律。

        【讨论】:

          【解决方案7】:

          只有当我确定其他开发人员使用该代码时不会产生误解时,我才会返回诸如null 引用或负索引值之类的值。类似于 LINQ 函数IEnumerable&lt;T&gt;.FirstOrDefaultIEnumerable&lt;T&gt;.First空集合抛出异常,因为预计会返回第一个元素,空集合是个例外情况

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-11-19
            • 2011-11-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多