【问题标题】:Always check parameters and throw exceptions始终检查参数并抛出异常
【发布时间】:2008-10-21 04:39:45
【问题描述】:

当参数不是您期望的那样时,您是否应该始终检查参数并在 .NET 中引发异常?例如。空对象还是空字符串?

我开始这样做,但后来认为如果在每个方法上都这样做会使我的代码膨胀很多。我应该检查私有和公共方法的参数吗?

我最终抛出了很多 ArgumentNullException("name") 异常,即使处理异常的代码实际上不能以编程方式做任何不同的事情,因为不能保证“name”将来不会改变。

我认为此信息仅在查看充满异常信息的日志时有用吗?

总是“为最坏的情况坦白”是最佳做法吗?

【问题讨论】:

    标签: .net exception


    【解决方案1】:

    我的两分钱:所有公共方法都应该始终检查传入参数的有效性。这通常称为“按合同编程”,是快速捕获错误并避免通过私有函数传播错误的好方法许多人会争辩说(包括我自己)不应该直接进行单元测试。至于抛出异常,如果你的函数或程序本身不能纠正错误,它应该抛出异常,因为它已经被抛出到无效状态。

    【讨论】:

      【解决方案2】:

      对于 public 方法,是的:绝对有必要检查你的论点;对于内部/私人电话,Eric Lippert 可能会将其归类为“愚蠢”(here);他的建议是不要抓住他们...修复代码!

      为避免代码膨胀,您可能需要考虑 AOP,例如 postsharp。为了说明这一点,Jon Skeet 有一个 postsharp 属性,用于检查空参数,here。然后(引用他的例子),你可以简单地属性方法:

      [NullArgumentAspect("text")]
      public static IEnumerable<char> GetAspectEnhancedEnumerable(string text)
      { /* text is automatically checked for null, and an ArgumentNullException thrown */ }
      

      这里另一个方便的技巧可能是扩展方法;扩展方法有一个奇怪的特性,你可以在空实例上调用它们......所以你可以执行以下操作(使用泛型而不是“对象”,这样你就不会意外地通过装箱值类型来调用它) :

      static void ThrowIfNull<T>(this T value, string name) where T : class
      {
          if (value == null) throw new ArgumentNullException(name);
      }
      // ...
      stream.ThrowIfNull("stream");
      

      而且你可以用超出范围等做类似的事情。

      【讨论】:

        【解决方案3】:

        没有什么比追踪“对象引用未设置为对象的实例”消息更糟糕的了。如果您的代码足够复杂,则很难知道发生了什么故障——尤其是在生产系统中,尤其是在罕见的边界条件下。显式异常在解决这些问题方面大有帮助。这是一种痛苦,但如果发生不好的事情确实发生,这是你不会后悔的事情之一。

        【讨论】:

          【解决方案4】:

          这取决于您的类/方法的使用者。如果这都是内部的,我会说它不那么重要。如果您有未知/第 3 方消费者,那么是的,您需要进行广泛的检查。

          【讨论】:

            【解决方案5】:

            对于这种情况,我的理念是通知并继续进行(如果适用)。 伪代码是:

            if value == not_valid then
            #if DEBUG
              log failure
              value = a_safe_default_value
            #elsif RELASE
              throw
            #endif
            end
            

            通过这种方式,您可以轻松地在开发过程中进行迭代,并让用户测试您的应用程序,而不会感到沮丧。

            【讨论】:

              【解决方案6】:

              我采用的方法是检查参数并在公开可见的成员上抛出异常 - 任何公开可见的我的意思是在程序集边界之外(所以 public 类上的任何 publicprotectedprotected internal 方法。这是因为您(通常)将程序集设计为作为一个自治单元运行,因此在程序集范围内的任何内容都应遵循如何在其中调用其他任何内容的规则。

              对于任何非公开可见的成员(即internalprivate 成员或类),我使用Debug.Assert 代替执行检查。这样,如果程序集中的任何调用者违反了您在开发/测试时立即发现的合同,但您在最终部署的解决方案中没有性能开销,因为这些语句在 RELEASE 构建中被删除。

              【讨论】:

                【解决方案7】:

                我的大部分经验是使用相对受限的系统,这种代码膨胀是不可取的。所以我自己的直觉是要么使用仅调试断言,要么完全忽略它。您希望在测试给您错误值的调用者期间会发生任何可能的无效输入,因此只要您在调试模式和发布模式下进行测试,您就会看到诊断信息。否则,您将在崩溃最终发生时对其进行调试。

                如果代码大小和性能无关紧要(并且在几乎所有代码中,简单的 null 或范围检查无论如何都不会影响性能),那么您在发布模式下的代码中保留的断言越多,您拥有的机会就越大无需在测试模式下重新创建错误即可诊断故障。这可以节省大量时间。特别是,如果您的产品是一个库,那么很大一部分“故障”报告是由于客户错误造成的,因此再多的预发布测试都无法阻止它们在野外发生。您越早向客户证明他们的代码是错误的,他们就能越早修复它,您就可以重新找到自己的错误。

                不过,在 C/C++ 中,我发现检查空指针的具体情况只是很小的帮助。如果有人给你一个指针,那么完整的有效性条件不是“不能为空”。它需要指向当前进程可读(也许也可写)到一定大小的内存,并且包含正确类型的对象,可能处于所有可能状态的某个子集中。它不需要被释放,不需要被其他地方的缓冲区溢出丢弃,可能不需要被另一个线程同时修改,等等。你不会在方法入口处测试所有这些,所以你仍然会错过 invalid参数。任何导致你或其他程序员认为“这个指针不是空的,所以它必须是有效的”,因为你只测试了一小部分的有效性条件,都是误导。

                如果您完全通过指针传递,那么您已经处于需要信任调用者不会给您垃圾的领域。拒绝一个特定的垃圾实例仍然让你相信调用者不会给你任何他们可以变出的更难检测的无数其他类型的垃圾。如果您发现空指针是您的特定调用者的一种常见垃圾,那么一定要对它们进行测试,因为它可以节省诊断系统其他地方的错误的时间。这取决于评估在调用者代码中发现具有“将空指针传递给我”症状的错误是否值得让您自己的代码膨胀(可能是二进制大小,当然还有源代码):如果这样的错误很少见,那么你可能会浪费时间和屏幕房地产检查他们。

                当然,在某些语言中,您不通过指针传递,调用者破坏内存的机会有限,因此垃圾的范围较小。但是在 Java 中,例如,传递错误的对象仍然是比传递错误的 null 更常见的编程错误。在任何情况下,如果将 Null 留给运行时发现并查看堆栈跟踪,则通常很容易诊断 Null。因此,即使在那里,空值检查的价值也非常有限。在 C++ 和 C# 中,您可以在禁止空值的情况下使用传递引用。

                这同样适用于您可能测试的任何其他特定无效输入以及任何语言。完整的前置条件和后置条件测试(如果可能的话)当然是另一回事,因为如果您可以测试整个通话合约,那么您的基础就更加稳固了。如果您可以使用编织或其他方式来断言合约,而无需添加到函数本身的源代码中,那就更好了。

                【讨论】:

                  【解决方案8】:

                  嗯,这取决于。如果您的代码无论如何都会选择 null 并引发异常,那么确保您拥有合理的清理代码可能更有意义。如果它可能没有被检测到,或者清理可能运行很长时间,或者可能有一个进程外调用(例如数据库),那么你最好不要尝试错误地改变世界,然后将它改回来。

                  【讨论】:

                    【解决方案9】:

                    我会将异常放在应用程序的最上层,因为最好在可以处理异常的地方捕获异常。如果可以处理异常;然后处理它。

                    这意味着低级别的东西通常会对传递的参数进行较少的错误处理。这样可以避免任何混乱并尊重性能。

                    这通常意味着与更高层的接口的方法得到更多的参数检查。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-07-21
                      • 1970-01-01
                      • 2011-06-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多