将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; }
我更喜欢断言方法,但是我不喜欢断言默认情况下处于关闭状态的事实。
该书最后指出:
In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default.
它实际上并没有涉及如何实施此限制?
无论哪种方式,我们中的任何人都有强烈的意见。
解决方案
回答
我通常不喜欢任何一种,因为这只是在减慢速度。无论如何,稍后都会引发NullPointerException,这将迅速导致用户发现他们正在将null传递给该方法。我曾经检查过,但是我40%的代码最终还是要检查代码,这时我认为这不值得漂亮的断言消息。
回答
It doesn't really go into how you would enforce this restriction?
如果它们传入null,则通过抛出ArgumentExcexception来强制实施。
if (p1 == null || p2 == null) { throw new IllegalArgumentException("Invalid argument for xProjection"); }
回答
我同意还是不同意wvdschel的帖子,这取决于他的具体说法。
当然,在这种情况下,此方法将在null
上崩溃,因此此处可能不需要显式检查。
但是,如果该方法仅存储传递的数据,并且我们稍后会调用其他一些方法来处理该数据,则尽早发现错误的输入是更快修复错误的关键。在此之后的某个时刻,可能会有无数种方式将不良数据恰巧提供给班级。这是在试图弄清事实之后老鼠如何进入房子,并试图在某个地方找到孔。
回答
我更喜欢使用断言。
我有一条规则,我只能在公共方法和受保护的方法中使用断言。这是因为我相信调用方法应确保将有效参数传递给私有方法。
回答
一般规则是,如果方法不希望使用" null"参数,则应抛出System.ArgumentNullException。抛出适当的"异常"不仅可以保护我们免受资源破坏和其他不良影响,而且还可以为代码用户提供指导,从而节省了调试代码的时间。
另请阅读有关防御性编程的文章
回答
@克里斯·卡歇尔,我会说完全正确。我要说的唯一一件事是分别检查参数,并让exeption报告参数为空的参数,因为这样可以更轻松地跟踪零值的来源。
@wvdschel哇!如果编写代码对我们来说太费力,则应考虑使用PostSharp(或者Java等效语言,如果可用的话)之类的东西,可以对程序集进行后处理并为我们插入参数检查。
回答
尽管它不是严格相关的,但我们可能要看一下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#。
回答
Speclooks非常有趣!
当类似的东西不可用时,我通常会通过运行时空检查和内部方法的断言来测试非私有方法。我没有在每个方法中显式地编写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); **/ /** ... **/ }
可以与编辑器宏或者代码处理脚本一起添加。
编辑:详细检查也可以通过这种方式添加,但是我认为自动添加单行要容易得多。
回答
同样不是立即使用,而是与Spec#的提及有关。有一个建议在Java的未来版本中添加"空安全类型":"增强的空处理Null安全类型"。
根据提案,方法将变为
public class MetricsCalculator { public double xProjection(#Point p1, #Point p2) { return (p2.x - p1.x) * 1.5; } }
其中,#Point是对类型为Point的对象的非null类型的引用。
回答
我认为,在方法开始时立即抛出CArgumentException
或者JavaIllegalArgumentException
是最清晰的解决方案。
对于方法签名中未声明的Runtime Exceptions异常,应始终保持谨慎。由于编译器不会强迫我们捕获这些错误,因此很容易忘记它们。确保我们具有某种"全部捕获"异常处理,以防止软件突然停止。那是用户体验中最重要的部分。
回答
处理此问题的最佳方法实际上是使用异常。最终,断言最终将给最终用户提供类似的体验,但是在向最终用户显示异常之前,开发人员无法调用代码来处理这种情况。 Ultimatley,我们希望确保尽早测试无效输入(尤其是在面向公众的代码中),并提供调用代码可以捕获的适当异常。
回答
In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default.
我发现,到目前为止,JetBrains的@ Nullable
和@ NotNull
注释方法可用于处理此问题。不幸的是,它是特定于IDE的,但确实干净且功能强大,IMO。
http://www.jetbrains.com/idea/documentation/howto.html
将此(或者类似的东西)作为java标准会非常好。
回答
在这里,使用断言和引发异常都是有效的方法。任何一种机制都可以用来指示编程错误,而不是运行时错误,如此处的情况。
- 断言具有性能优势,因为它们通常在生产系统上被禁用。
- 异常具有安全性的优点,因为始终执行检查。
选择实际上取决于项目的开发实践。整个项目需要确定一个断言策略:如果选择在所有开发过程中启用断言,那么我会说使用断言来检查生产系统中的这种无效参数,由于无论如何,编程错误都不太可能以有意义的方式被捕获和处理,因此它的行为就像断言一样。
但是实际上,我知道很多开发人员不相信断言会在适当的时候启用,因此选择了抛出NullPointerException的安全性。
当然,如果我们不能为代码实施策略(例如,如果我们正在创建库,并且这取决于其他开发人员如何运行代码),则应该选择对这些代码抛出NullPointerException的安全方法库API的一部分的方法。
回答
稍微偏离主题,但我认为findbug的一个非常有用的功能是能够注释方法的参数,以描述哪些参数不应传递空值。
使用代码的静态分析,findbug可以随后指出使用潜在的空值调用该方法的位置。
这有两个优点:
- 注释描述了如何调用该方法的意图,并帮助了文档
- FindBugs可以指向该方法的潜在问题调用者,从而使我们可以跟踪潜在的错误。
仅当我们有权访问调用方法的代码时才有用...但是通常是这种情况。
回答
以Java方式,假设null来自编程错误(即,永远不要超出测试阶段),然后让系统将其抛出,或者如果有副作用达到该点,请在开始时检查null,然后抛出IllegalArgumentException或者NullPointerException。
如果null可能来自实际的特殊情况,但我们不想为此使用检查的异常,那么我们肯定想在方法开始时使用IllegalArgumentException路由。
回答
由于离题似乎已成为话题,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" }