【问题标题】:Passing null to a method [closed]将 null 传递给方法[关闭]
【发布时间】:2010-09-07 03:10:36
【问题描述】:

我正在阅读优秀的Clean Code

一个讨论是关于将空值传递给方法。

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}
...
calculator.xProjection(null, new Point(12,13));

它代表了不同的处理方式:

public double xProjection(Point p1, Point p2) {
    if (p1 == null || p2 == null) {
        throw new IllegalArgumentException("Invalid argument for xProjection");
    }
    return (p2.x - p1.x) * 1.5;
}

public double xProjection(Point p1, Point p2) {
    assert p1 != null : "p1 should not be null";
    assert p2 != null : "p2 should not be null";
    return (p2.x - p1.x) * 1.5;
}

我更喜欢assertions 方法,但我不喜欢断言默认关闭的事实。

这本书最后说:

在大多数编程语言中,没有很好的方法来处理调用者意外传递的空值。因为是这样,理性的做法是默认禁止传递null。

它并没有真正涉及您将如何执行此限制?

你们中的任何人都有强烈的意见吗?

【问题讨论】:

    标签: java null assert


    【解决方案1】:

    一般规则是,如果您的方法不需要 null 参数,那么您应该抛出 System.ArgumentNullException。正确使用 Exception 不仅可以保护您免受资源损坏和其他不良事件的影响,还可以为您的代码用户提供指导,从而节省调试代码的时间。

    还可以阅读Defensive programming上的一篇文章

    【讨论】:

    • 这是一个很好的 c# 答案,不如 java 答案:)
    【解决方案2】:

    使用断言和抛出异常都是有效的方法。任何一种机制都可以用来指示编程错误,而不是运行时错误,就像这里的情况一样。

    • 断言具有性能优势,因为它们通常在生产系统上被禁用。
    • 异常具有安全优势,因为始终会执行检查。

    选择实际上取决于项目的开发实践。整个项目需要决定一个断言策略:如果选择是在所有开发过程中启用断言,那么我会说使用断言来检查这种无效参数 - 在生产系统中,由于引发 NullPointerException无论如何,编程错误不太可能以有意义的方式被捕获和处理,因此其行为就像一个断言。

    但实际上,我知道很多开发人员不相信断言会在适当的时候启用,因此选择抛出 NullPointerException 的安全性。

    当然,如果您不能为您的代码强制执行策略(例如,如果您正在创建一个库,那么这取决于其他开发人员如何运行您的代码),您应该选择安全的 throwing 方法属于库 API 的那些方法的 NullPointerException。

    【讨论】:

      【解决方案3】:

      也不是立即使用,但与 Spec# 的提及有关...有一个提议在 Java 的未来版本中添加“空安全类型”:"Enhanced null handling - Null-safe types"

      根据提案,您的方法将变为

      public class MetricsCalculator {
          public double xProjection(#Point p1, #Point p2) {
              return (p2.x - p1.x) * 1.5;
          }
      }
      

      其中#Point 是对Point 类型对象的非null 引用类型。

      【讨论】:

      • 我真的不知道我是否希望它成为 Java 的一部分,但它非常有趣。
      【解决方案4】:

      它并没有真正涉及您将如何执行此限制?

      如果它们传入 null,则通过抛出 ArgumentExcexception 来强制执行它。

      if (p1 == null || p2 == null) {
          throw new IllegalArgumentException("Invalid argument for xProjection");
      }
      

      【讨论】:

        【解决方案5】:

        在大多数编程语言中,没有很好的方法来处理调用者意外传递的空值。因为是这样,理性的做法是默认禁止传递null。

        我发现 JetBrains' @Nullable@NotNull 注释方法是迄今为止处理这个问题的最巧妙的方法。不幸的是,它是特定于 IDE 的,但非常干净且功能强大,IMO。

        http://www.jetbrains.com/idea/documentation/howto.html

        将这个(或类似的东西)作为 java 标准会非常好。

        【讨论】:

          【解决方案6】:

          我更喜欢使用断言。

          我有一条规则,我只在公共方法和受保护方法中使用断言。这是因为我相信调用方法应该确保它将有效的参数传递给私有方法。

          【讨论】:

          • 来自docs.oracle.com/javase/7/docs/technotes/guides/language/… 不要在公共方法中使用断言来检查参数。参数检查通常是方法的已发布规范(或合同)的一部分,无论断言是启用还是禁用,都必须遵守这些规范。使用断言进行参数检查的另一个问题是错误的参数会导致适当的运行时异常(IllegalArgumentException、IndexOutOfBoundsException 等)。断言失败不会引发适当的异常。
          【解决方案7】:

          Spec# 看起来很有趣!

          当这样的东西不可用时,我通常会使用运行时空值检查和内部方法的断言来测试非私有方法。我没有在每个方法中显式编码 null 检查,而是将其委托给具有检查 null 方法的实用程序类:

          /**
           * Checks to see if an object is null, and if so 
           * generates an IllegalArgumentException with a fitting message.
           * 
           * @param o The object to check against null.
           * @param name The name of the object, used to format the exception message
           *
           * @throws IllegalArgumentException if o is null.
           */
          public static void checkNull(Object o, String name) 
              throws IllegalArgumentException {
             if (null == o)
                throw new IllegalArgumentException(name + " must not be null");
          }
          
          public static void checkNull(Object o) throws IllegalArgumentException {
             checkNull(o, "object");
          } 
          
          // untested:
          public static void checkNull(Object... os) throws IllegalArgumentException {
             for(Object o in os) checkNull(o);  
          }
          

          然后检查变成:

          public void someFun(String val1, String val2) throws IllegalArgumentException {
             ExceptionUtilities.checkNull(val1, "val1");
             ExceptionUtilities.checkNull(val2, "val2");
          
             /** alternatively:
             ExceptionUtilities.checkNull(val1, val2);
             **/
          
             /** ... **/
          } 
          

          可以使用编辑器宏或代码处理脚本添加。 编辑:也可以通过这种方式添加详细检查,但我认为自动添加单行要容易得多。

          【讨论】:

            【解决方案8】:

            虽然不是严格相关,但您可能需要查看Spec#

            我认为它仍在开发中(由 Microsoft),但有一些 CTP 可用,而且看起来很有希望。基本上它允许你这样做:

              public static int Divide(int x, int y)
                requires y != 0 otherwise ArgumentException; 
              {
              }
            

              public static int Subtract(int x, int y)
                requires x > y;
                ensures result > y;
              {
                return x - y;
              } 
            

            它还提供了其他功能,例如 Notnull 类型。它建立在 .NET Framework 2.0 之上,并且完全兼容。如您所见,语法是 C#。

            【讨论】:

              【解决方案9】:

              @Chris Karcher 我会说绝对正确。我唯一要说的是分别检查参数并让异常报告为空的参数,因为它使跟踪空值的来源变得更加容易。

              @wvdschel 哇!如果编写代码对您来说太费力了,您应该研究类似PostSharp(或Java 等效项,如果有的话),它可以对您的程序集进行后处理并为您插入参数检查。

              【讨论】:

                【解决方案10】:

                由于跑题似乎已成为话题,Scala 对此采取了一种有趣的方法。假定所有类型都不为 null,除非您明确地将其包装在 Option 中以指示它可能为 null。所以:

                //  allocate null
                var name : Option[String]
                name = None
                
                //  allocate a value
                name = Any["Hello"]
                
                //  print the value if we can
                name match {
                  Any[x] => print x
                  _ => print "Nothing at all"
                }
                

                【讨论】:

                • 有趣 :-) 需要阅读 scala。
                【解决方案11】:

                我一般都不想这样做,因为它只会减慢速度。无论如何,稍后都会抛出 NullPointerExceptions,这将很快导致用户发现他们正在将 null 传递给该方法。我曾经检查过,但我 40% 的代码最终都在检查代码,此时我认为它不值得漂亮的断言消息。

                【讨论】:

                  【解决方案12】:

                  我同意或不同意wvdschel's post,这取决于他具体说了什么。

                  在这种情况下,这个方法肯定会在null 上崩溃,所以这里可能不需要显式检查。

                  但是,如果该方法只是存储传递的数据,并且您稍后会调用其他一些方法来处理它,尽早发现错误输入是更快修复错误的关键。在那之后,可能有无数种方式将坏数据碰巧提供给您的班级。这有点像试图弄清楚老鼠是如何在事后进入你的房子的,试图在某个地方找到洞。

                  【讨论】:

                    【解决方案13】:

                    有点跑题了,但是findbugs 的一个我认为非常有用的特性是能够注释方法的参数来描述哪些参数不应该被传递一个空值。

                    使用您的代码的静态分析,findbugs 可以指出调用该方法的位置,其中可能存在 null 值。

                    这有两个好处:

                    1. 注释描述了您应该如何调用该方法的意图,以帮助文档
                    2. FindBugs 可以指向方法的潜在问题调用者,让您能够追踪潜在的错误。

                    仅当您可以访问调用您的方法的代码时才有用,但通常是这种情况。

                    【讨论】:

                      【解决方案14】:

                      在方法的开头抛出 C# ArgumentException 或 Java IllegalArgumentException 在我看来是最清晰的解决方案。

                      应该始终小心运行时异常 - 未在方法签名上声明的异常。由于编译器不会强制您捕获这些内容,因此很容易忘记它们。确保您有某种“包罗万象”的异常处理,以防止软件突然停止。这是用户体验中最重要的部分。

                      【讨论】:

                        【解决方案15】:

                        处理这个问题的最好方法是使用异常。最终,断言最终会为最终用户提供类似的体验,但在向最终用户显示异常之前,开发人员无法调用您的代码来处理这种情况。最后,您希望确保尽早测试无效输入(尤其是在面向公众的代码中)并提供调用代码可以捕获的适当异常。

                        【讨论】:

                          【解决方案16】:

                          在 Java 方式中,假设 null 来自编程错误(即永远不应超出测试阶段),然后让系统抛出它,或者如果有副作用到达该点,请在以下位置检查 null开始并抛出 IllegalArgumentException 或 NullPointerException。

                          如果 null 可能来自实际的 exceptional 案例,但您不想为此使用已检查的异常,那么您肯定希望在方法的开头使用 IllegalArgumentException 路线.

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2022-01-03
                            • 2013-01-25
                            • 1970-01-01
                            • 2012-11-18
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2012-03-18
                            相关资源
                            最近更新 更多