将null传递给方法

时间:2008-08-28 13:34:40

标签: java null assert

我正在阅读优秀的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。因为这种情况,理性的方法是默认禁止传递null。

它没有真正涉及如何强制执行此限制?

你们中的任何一方都有强烈的意见。

16 个答案:

答案 0 :(得分:8)

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

另请阅读Defensive programming

上的文章

答案 1 :(得分:4)

也没有立即使用,但与提到Spec#有关......有人建议在未来版本的Java中添加“null-safe types”:"Enhanced null handling - Null-safe types"

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

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

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

答案 2 :(得分:4)

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

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

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

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

当然,如果您不能为您的代码强制实施策略(例如,如果您正在创建库,并且依赖于其他开发人员如何运行您的代码),您应该选择安全的投掷方法对于属于库API的那些方法的NullPointerException。

答案 3 :(得分:3)

  

它没有真正涉及如何强制执行此限制?

如果传入空值,则通过抛出ArgumentExcexception来强制执行。

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

答案 4 :(得分:2)

我更喜欢使用断言。

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

答案 5 :(得分:2)

Spec#看起来非常有趣!

当这样的东西不可用时,我通常使用运行时空检查和内部方法的断言来测试非私有方法。我没有在每个方法中显式地编写空检查,而是使用check 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);
   **/

   /** ... **/
} 

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

答案 6 :(得分:2)

  

在大多数编程语言中,没有好办法处理调用者意外传递的null。因为这种情况,理性的方法是默认禁止传递null。

到目前为止,我找到了 JetBrains '@Nullable@NotNull注释方法来处理这种最巧妙的方法。不幸的是,这是IDE特定的,但非常干净和强大,IMO。

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

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

答案 7 :(得分:1)

我通常不愿意这样做,因为它只会减慢速度。无论如何都会抛出NullPointerExceptions,这将很快导致用户发现他们将null传递给该方法。我曾经检查过,但是我的代码中有40%最终都在检查代码,此时我认为它不值得使用好的断言消息。

答案 8 :(得分:1)

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

在这种情况下,确定,此方法将在null崩溃,因此可能不需要显式检查。

但是,如果该方法只是存储传递的数据,并且稍后会调用其他方法来处理它,尽早发现错误输入是更快修复错误的关键。在稍后的时间点,可能会有无数种方法可以将错误数据发送给您的班级。有点想弄清楚老鼠是怎么进入你家后的,试图在某个地方找到那个洞。

答案 9 :(得分:1)

@Chris Karcher我会说绝对正确。我唯一要说的是分别检查params并让exeption报告为null的param,因为它可以更容易地跟踪null的来源。

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

答案 10 :(得分:1)

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

我认为它仍处于开发阶段(微软),但有些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#。

答案 11 :(得分:1)

稍微偏离主题,但我认为findbugs的一个特性非常有用,是能够注释方法的参数来描述哪些参数不应传递为空值。

使用代码的静态分析, findbugs 可以指出调用方法的位置,其中包含可能为空的值。

这有两个好处:

  1. 注释描述了您应该如何调用该方法的意图,帮助文档
  2. FindBugs可以指向该方法的潜在问题调用者,允许您追踪潜在的错误。
  3. 仅在您有权访问调用方法的代码时才有用,但通常情况就是如此。

答案 12 :(得分:1)

由于偏离主题似乎已经成为主题,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"
}

答案 13 :(得分:0)

在方法开头就把C#ArgumentException或Java IllegalArgumentException看作是最清晰的解决方案。

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

答案 14 :(得分:0)

处理此问题的最佳方法是使用例外。最终,断言最终将为最终用户提供类似的体验,但是在向最终用户显示异常之前,开发人员无法调用代码来处理这种情况。 Ultimatley,您希望尽可能早地测试无效输入(特别是在面向公众的代码中),并提供调用代码可以捕获的相应异常。

答案 15 :(得分:0)

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

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