我正在阅读优秀的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。
它没有真正涉及如何强制执行此限制?
你们中的任何一方都有强烈的意见。
答案 0 :(得分:8)
一般规则是,如果您的方法不期望null
参数,那么您应该抛出System.ArgumentNullException。投掷正确Exception
不仅可以保护您免受资源损坏和其他不良事件的影响,还可以为您的代码用户提供指导,从而节省调试代码所花费的时间。
答案 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)
@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 可以指出调用方法的位置,其中包含可能为空的值。
这有两个好处:
仅在您有权访问调用方法的代码时才有用,但通常情况就是如此。
答案 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路由。