我一直在Java代码中使用了很多防御性空检查。尽管它们很好地达到了自己的目的(大多数时候(,但它们与"丑陋"的代码进行了巨大的权衡。
一直把这些空检查放进去真的有意义吗?例如:
if(object == null){
log.error("...")
throw new SomeRuntimeException("");
} else{
object.someMethod();
}
实际上,上面的代码段相当于语句object.someMethod();
如果object
的值为 null,则在这两种情况下都会引发异常(后面的 NullpointerException(。
屏蔽 NullpointerExcetion (NPE( 并抛出一些自定义 RunTimeException(如上面的代码片段(真的有意义吗? 我观察到一些开发人员将NPE视为邪恶,经常试图找出防御方法来掩盖它,并用一些自定义例外来掩盖它。 真的需要这种掩蔽吗?
问题
- 如果我们无法从该空状态恢复,那么允许我们的代码通过 NPE 失败是不是很有意义?(在上面的示例中,这似乎是一个无法恢复的情况。
- 如果没有,为什么?
在像您发布的情况中,检查没有任何好处。你正在用另一个没有额外信息或价值的 RuntimeException 替换另一个 RuntimeException(对于不熟悉你的代码的人来说,可以说少一点,因为每个人都知道 NPE 是什么,并不是每个人都知道你的 SomeRuntimeException 是什么(。
我认为显式检查的主要两次是:
- 当我想抛出一个选中的异常而不是未选中的异常时。
- 当我想在实际使用引用之前检查空值时。
当我只打算存储引用以供以后使用时,第二种情况尤其重要:例如,在构造函数中。在这种情况下,当有人使用该引用并触发 NPE 时,可能很难找到该 null 最初是如何到达那里的。通过在字段中保存之前对其进行检查,您更有可能找到根本原因。事实上,这是一种足够常见的模式,JDK 甚至有一个requireNonNull
的助手。
如果你看很多成熟的、高声誉的项目,我想你会发现"只要使用它,如果它发生,就让它发生"的模式很常见。举一个例子,JDKCollections.sort
的代码很简单list.sort(null)
,其中list
是方法参数。如果为 null,则该行将引发 NPE。
对于内部代码,一堆空检查是毫无用处的。如果要将 null 传递给不期望它的方法,则仅让它因 NPE 失败是可以接受的,而不是尝试防止错误。如果您的代码是从您无法控制的其他代码调用的,则可以在方法的开头断言 (Objects.requireNonNull(,或者提供一个 Javadoc 来说明传入 null 的结果。后面的方法在 JDK 代码库中普遍使用。
NPE被认为是坏的,因为它们没有为刚刚发生的问题提供"语义"。关于 Java 应用程序中的任何代码段都可能引发 NPE。因此,当它发生时,您没有立即知道导致 NPE 的原因:缺少用户条目?编程错误?滥用的外部依赖关系?
由于这种可能的原因多种多样,在上层捕获 NPE 来处理问题将是非常糟糕的做法
因此,当您有可能时,就像您给出的示例一样,在编写代码时,您就是知道对象 == null 时这意味着什么的人。因此,如果您选择抛出具有语义含义的异常,则可以在上层专门捕获它,并在功能上处理此特殊情况。
关于样板如果(... == null(否则...,如果您使用Java 8和Optional,则可以避免它
我写了一个个人类ArgumentChecker.java,其中包含许多常用的简单静态方法来检查异常,如checkPositive(double value(,checkEquals(int v1,int v2(,checkNonDecreasingOrder(int... args(,checkNonNull(Object obj(,...等。下面有两个例子。
public static void checkAllEqualsTo(int theValue, int... values){
for (int i = 0; i < values.length; i++) { // StringUtils is also a personal simple class containing some wrapped methods
if (values[i] != theValue)
throw new IllegalStateException(NOT_ALL_EQUAL_EXCEPTION + "; values = " + StringUtils.toString(values, values.length) + ", theValue = " + theValue);
}
}
public static void checkAllEquals(int... values){
if (values.length == 0){
return;
}
checkAllEqualsTo(values[0], values);
}
这些方法是可重用的,对这些异常检查方法的方法调用也简洁易读。
对于此问题,代码变为:
ArgumentCheck.checkNonNull(object);
object.someMethod();