将null传递给方法

我正在阅读优秀的清洁代码

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

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; } 

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

这本书最后说:

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

它没有真正涉及你如何执行这个限制?

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

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

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

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

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

当然,如果你不能为你的代码强制执行一个策略(例如,如果你正在创建一个库,那么依赖于其他开发人员如何运行你的代码),你应该选择为那些人抛出NullPointerException的安全方法作为库API的一部分的方法。

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

另请阅读有关防御性编程的文章

也没有立即使用,但与提到Spec#有关……有人建议在未来版本的Java中添加“null-safe types”: “增强的空值处理 – 空安全类型” 。

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

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

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

它没有真正涉及你如何执行这个限制?

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

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

我更喜欢使用断言。

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

Spec#看起来非常有趣!

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

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

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

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

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

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

虽然它没有严格的相关性,但您可能需要查看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类型等其他function。 它构建于.NET Framework 2.0之上,并且完全兼容。 你可能会看到,合成文件是C#。

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

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

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

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

这有两个好处:

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

仅在您有权访问调用方法的代码时才有用…但通常情况就是如此。

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

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

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

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

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

在方法开头就把C# ArgumentException或Java IllegalArgumentException打包成了最清晰的解决方案。

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

处理这个的最好方法就是使用exception。 最终,断言最终会给最终用户提供类似的体验,但是在向最终用户显示exception之前,开发人员无法调用代码来处理这种情况。 Ultimatley,您希望确保尽早测试无效输入(特别是在面向公众的代码中),并提供调用代码可以捕获的相应exception。

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

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