先决条件库为 notNull 检查抛出 IllegalArgumentException

Preconditions library to throw IllegalArgumentException for notNull check

你知道 Apache Commons Validate or Guava Preconditions that would throw IllegalArgumentException instead of NullPointerException when checking if object is not null (except Spring Assert) 的一些不错的替代品吗?


我知道 Javadocs 说:

Applications should throw instances of this class [NullPointerException] to indicate other illegal uses of the null object.

尽管如此,我就是不喜欢它。对我来说,NPE 总是意味着我只是忘了在某处保护空引用。我的眼睛训练有素,我可以发现它以每秒几页的速度浏览日志,如果我这样做,我的脑海中总会启用错误警报。因此,将它抛到我期望 IllegalArgumentException 的地方会让我很困惑。

假设我有一颗豆子:

public class Person {
  private String name;
  private String phone;
  //....
}

和服务方式:

public void call(Person person) {
  //assert person.getPhone() != null
  //....
}

在某些情况下可能没关系,一个人没有 phone(我的祖母没有)。但是如果你想打电话给这样的人,对我来说就是调用 call 方法并传递 IllegalArgument 。查看层次结构 - NullPointerException 甚至不是 IllegalArgumentException 的子类。它基本上告诉您 - 您再次尝试在空引用 .

上调用 getter

此外,已经进行了讨论,this 很好的答案我完全支持。所以我的问题只是 - 我是否需要做这样丑陋的事情:

Validate.isTrue(person.getPhone() != null, "Can't call a person that hasn't got a phone");

按照我的方式进行,或者是否有一个库会抛出 IllegalArgumentException 以进行 notNull 检查?

您可以轻松做到这一点:

if (person.getPhone() == null) {
    throw new IllegalArgumentException("Can't call a person that hasn't got a phone");
}

其他程序员很清楚您的意思,并且完全按照您的意愿去做。

PreconditionscheckArgument 呢?

public void call(Person person) {
    Preconditions.checkArgument(person.getPhone() != null);
    // cally things...
}

checkArgument throws IllegalArgumentException 而不是 NullPointerException.

我不知道。我只是通过一个简洁的调用来实现你自己的行为,模仿 Guava 的实现,但调整异常类型。

class Preconditionz {
    public static <T> T checkNotNull(T reference, Object errorMessage) {
        if (reference == null) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
        return reference;
    }
}

我喜欢继续import static这些非常常用的方法,所以你可以把它们称为超级简洁。

import static com.whatever.util.Preconditionz.checkNotNull;

// ...

public void call(Person person) {
    checkNotNull(person, "person");
    checkNotNull(person.getPhone(), "person.phone");
    // ...
}

根据您的环境,您可能希望将其命名为 checkNotNull2,这样可以更轻松地在 IDE 中通过自动完成添加导入,或者让您将其与标准 checkNotNull 一起使用].

感谢 Olivier Grégoire、Louis Wasserman、CollinD 和 Captain Man 的精彩评论,我想我在 SO 上又学到了一些东西。 这些标准通常是一个强有力且充分的理由,因为它们使通用语言程序员始终能够正确理解,但在这种特殊情况下,我有点怀疑,也许围绕 NPE 设置的规则不太好。 Java 是一门古老的语言,它的一些特性有点不走运(我不想说错,这可能是判断力太强了)——比如 checked exceptions,尽管你可能也不同意.现在我觉得这个疑惑解决了,我应该:

  • 在特定上下文中抛出 IllegalArgumentException,我可以从业务角度而不是从业务角度判断 null 值错误的原因。例如在服务方法 public void call(Person person) 中,我知道 phone 数字为空对系统意味着什么。
  • 当我只知道这里的空值是错误的并且迟早会导致NullPointerException时抛出一个NullPointerException,但是在特定的上下文中我不知道从业务角度来看这意味着什么。示例是 Guavas 不可变集合。当您构建此类并尝试添加一个空值元素时,它会抛出一个 NPE。它不明白这个值对你意味着什么,它太笼统了,但它只知道它在这里是错误的,所以它决定也立即告诉你这个,并提供一些更合适的消息,以便你可以更有效地识别问题。

考虑到以上几点,我会说在 public void call(Person person) 示例中做出断言的最佳选择就像 Captain Man 建议的那样:

Preconditions.checkArgument(person.getPhone() != null, "msg");

检查参数是此方法的一个好名字 - 很明显,我正在根据人员参数检查业务合同合规性,并且很明显,如果它失败,我会期待 IllegalArgumentException。这个名字比 Apache Commons 的 Validate.isTrue 更好。另一方面,说 Validate.notNull 或 Preconditions.checkNotNull 表明我正在检查空引用,我实际上期待 NPE。

所以最后的答案是 - 没有这样的 nice 库,不应该这样,因为这会造成混淆。 (并且 Spring Assert 应该更正)。

由于这个问题的主题演变为"Correct usage of IllegalArgumentException and NullpointerException",我想指出Effective Java Item 60(第二个版本):

Arguably, all erroneous method invocations boil down to an illegal argument or illegal state, but other exceptions are standardly used for certain kinds of illegal arguments and states. If a caller passes null in some parameter for which null values are prohibited, convention dictates that NullPointerException be thrown rather than IllegalArgumentException. Similarly, if a caller passes an out-ofrange value in a parameter representing an index into a sequence, IndexOutOfBoundsException should be thrown rather than IllegalArgumentException.

您可以将 valid4j 与 hamcrest-matchers 一起使用(在 Maven Central 上以 org.valid4j:valid4j 的形式找到)。 'Validation' class 支持常规输入验证(即抛出可恢复的异常):

import static org.valid4j.Validation.*;

validate(argument, isValid(), otherwiseThrowing(InvalidException.class));

链接:

在 side-note 上:该库还支持 pre- 和 post-conditions(真的像断言),如果需要,可以注册您自己的自定义全局策略:

import static org.valid4j.Assertive.*;

require(x, greaterThan(0)); // throws RequireViolation extends AssertionError
...
ensure(r, notNullValue()); // throws EnsureViolation extends AssertionError

看看https://github.com/cowwoc/requirements.java/(我是作者)。您可以使用 withException() 覆盖默认异常类型,如下所示:

new Verifiers().withException(IllegalArgumentException.class).requireThat(name, value).isNotNull();