题
我正在阅读优秀的 干净的代码
一个讨论是关于将空值传递到方法中。
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。
它并没有真正涉及您将如何执行此限制?
不管怎样,你们中有人有强烈的意见吗?
解决方案
使用断言和抛出异常都是有效的方法。任一机制都可用于指示编程错误,而不是运行时错误,如此处的情况。
- 断言具有性能优势,因为它们通常在生产系统上被禁用。
- 异常具有安全性的优点,因为总是执行检查。
选择实际上取决于项目的开发实践。整个项目需要决定断言策略:如果选择在所有开发过程中启用断言,那么我会说使用断言来检查这种无效参数 - 在生产系统中,由于编程错误而引发的 NullPointerException 不太可能被捕获和处理无论如何,以一种有意义的方式,所以就像一个断言一样。
但实际上,我知道很多开发人员不相信断言会在适当的时候启用,因此选择抛出 NullPointerException 来确保安全。
当然,如果您无法为您的代码强制执行策略(例如,如果您正在创建一个库,因此依赖于其他开发人员运行您的代码的方式),那么您应该选择抛出 NullPointerException 的安全方法属于库 API 一部分的方法。
其他提示
一般规则是如果您的方法不期望 null
那么你应该抛出参数 系统参数NullException. 。正确投掷 Exception
不仅可以保护您免受资源损坏和其他不良情况的影响,还可以为您的代码用户提供指南,从而节省调试代码的时间。
另请阅读一篇文章 防御性编程
也不是立即使用的,但与 Spec# 的提及有关......有人建议在 Java 的未来版本中添加“空安全类型”: “增强的 null 处理 - Null 安全类型”.
根据该提案,您的方法将变为
public class MetricsCalculator {
public double xProjection(#Point p1, #Point p2) {
return (p2.x - p1.x) * 1.5;
}
}
在哪里 #Point
是非类型null
对类型对象的引用 Point
.
它并没有真正涉及您将如何执行此限制?
你可以通过抛出一个来强制执行它 参数异常 如果他们传入 null。
if (p1 == null || p2 == null) {
throw new IllegalArgumentException("Invalid argument for xProjection");
}
我更喜欢使用断言。
我有一条规则,只在公共和受保护的方法中使用断言。这是因为我相信调用方法应该确保它将有效参数传递给私有方法。
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);
**/
/** ... **/
}
那 可以使用编辑器宏或代码处理脚本添加。编辑: 详细检查也可以通过这种方式添加,但我认为自动添加单行要容易得多。
在大多数编程语言中,没有好的方法来处理调用者意外传递的 null。因为是这种情况,所以合理的做法是默认禁止传递 null。
我发现 捷脑公司' @Nullable
和 @NotNull
到目前为止,处理这个问题的注释方法是最巧妙的。不幸的是,它是特定于 IDE 的,但在我看来确实干净且强大。
http://www.jetbrains.com/idea/documentation/howto.html
拥有这个(或类似的东西)作为 java 标准会非常好。
虽然它并不严格相关,但您可能想看看 规格#.
我认为它仍在开发中(由 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#。
@Chris Karcher 我想说绝对正确。我唯一要说的是单独检查参数,并让异常报告为空的参数,因为它使得跟踪空值的来源变得更加容易。
@wvdschel 哇!如果编写代码对您来说太费力,您应该考虑类似的内容 后锐利 (或者 Java 等效项,如果可用的话)它可以后处理您的程序集并为您插入参数检查。
由于离题似乎已成为主题,Scala 对此采取了一种有趣的方法。所有类型都假定不为 null,除非您显式地将其包装在 Option
表明它可能为空。所以:
// 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"
}
我通常不喜欢这样做,因为它只会减慢速度。无论如何,稍后都会抛出 NullPointerException,这将很快导致用户发现他们将 null 传递给该方法。我曾经检查过,但 40% 的代码最终都在检查代码,此时我认为不值得使用漂亮的断言消息。
我同意或不同意 wvdschel 的帖子, ,这取决于他具体说的是什么。
在这种情况下,这个方法肯定会崩溃 null
所以这里可能不需要显式检查。
但是,如果该方法只是存储传递的数据,并且稍后调用其他一些方法来处理它, 尽早发现错误的输入是更快修复错误的关键. 。到那时,可能会通过多种方式向您的班级提供不良数据。这有点像试图弄清楚老鼠是如何在事后进入你的房子的,试图在某个地方找到洞。
有点偏离主题,但有一个特点 查找错误 我认为非常有用的是能够注释方法的参数来描述哪些参数不应传递空值。
使用代码的静态分析, 查找错误 然后可以指出使用可能为空值调用该方法的位置。
这样做有两个优点:
- 该注释描述了您对如何调用该方法的意图,有助于文档记录
- FindBugs 可以指出该方法的潜在问题调用者,使您能够追踪潜在的错误。
仅当您有权访问调用您的方法的代码时才有用,但通常是这种情况。
扔C# ArgumentException
, ,或Java IllegalArgumentException
在我看来,该方法的开头是最清晰的解决方案。
人们应该始终小心运行时异常 - 未在方法签名上声明的异常。由于编译器不会强制您捕获这些内容,因此很容易忘记它们。确保您有某种“捕获所有”异常处理,以防止软件突然停止。这是用户体验中最重要的部分。
处理这个问题的最佳方法实际上是使用异常。最终,这些断言将给出一个 相似的 向最终用户提供经验,但在向最终用户显示异常之前,无法为调用代码的开发人员提供处理这种情况的方法。最终,您希望确保尽早测试无效输入(尤其是在面向公众的代码中)并提供调用代码可以捕获的适当异常。
以 Java 的方式,假设 null 来自编程错误(即永远不应该超出测试阶段),然后让系统抛出它,或者如果有达到该点的副作用,请在开始时检查 null 并抛出 IllegalArgumentException 或 NullPointerException。
如果 null 可以来自实际的 例外在这种情况下,但您不想为此使用检查异常,那么您肯定希望在方法开头使用 IllegalArgumentException 路线。