为 notNull 检查抛出 IllegalArgumentException 的前提条件库

Posted

技术标签:

【中文标题】为 notNull 检查抛出 IllegalArgumentException 的前提条件库【英文标题】:Preconditions library to throw IllegalArgumentException for notNull check 【发布时间】:2015-07-14 08:35:20 【问题描述】:

您知道Apache Commons Validate 或Guava Preconditions 的一些不错的替代方法,在检查对象是否不为空 时会抛出IllegalArgumentException 而不是NullPointerException(Spring Assert 除外)?


我知道Javadocs 说:

应用程序应抛出此类 [NullPointerException] 的实例以指示其他 非法使用空对象。

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

假设我有一颗豆子:

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

和服务方法:

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

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

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

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

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

【问题讨论】:

不清楚您所说的“正确处理的情况”是什么意思。当然,这不是对null 的意外取消引用,但它相当于同一件事:正在调用一个方法,并为一个不能为null 的参数传入null,这几乎是程序员的错误. 当然,但在这种情况下,处理了 NPE(在我的解释中),不处理 IllegalArgumentException。实际上它通常是由一些 servlet 级别的异常处理程序或 smth. FWIW,JDK 树立了一个非常坚定的先例,这就是 it 期望处理无效空值的方式。 我仍然可能想找到一个与这些期望相反的库:) "将它扔到我期望 IllegalArgumentException 的地方会让我很困惑。" - 对于维护您的代码的人来说,收到 IAE 而不是 NPE 会很混乱......只需使用标准。甚至 java.util.Objects.requireNotNull & friends 也会抛出 NPE。 【参考方案1】:

您可以轻松做到这一点:

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

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

【讨论】:

呵呵,那更丑了:) 我的“丑”例子也做了我不想做的事情,而且也很清楚。我的问题是关于一个库,它抛出 IllegalArgumentException 而不是 NPE(就像 guava 和 apache-commons 一样)进行 notNull 检查。 其实这是Java中检查参数的正常方式,它不依赖于任何库。据我所知,没有理由不使用它。另外,我不知道有任何编码风格指南说这很丑。 我的风格指南是这么说的;)但是,说真的,我也经常像你建议的那样做。我的问题只是略有不同。目标是进行简短、整洁的检查,例如 Validate.notNull(person.getPhone(), "msg");【参考方案2】:

PreconditionscheckArgument 呢?

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

checkArgumentthrowsIllegalArgumentException 而不是NullPointerException

【讨论】:

是的,apache-commons Validate.isTrue 也抛出 IllegalArgumentException,但 Validate.notNull 抛出 NPE 让我很失望。 Preconditions.checkArgument( ... != null); 可能不是您想要的,但它仍然比Validate.isTrue( ... != null); 读起来更好(在我看来),因为很明显它与来自名称的前提条件和参数。 嗯,是的,这可能更好。 +1 ;) @macias NullPointerException 通常比 IllegalArgumentException 更推荐用于空参数(请参阅Effective Java 项目 60 as above)。它在标准库中也很常见,例如在URI.create()。这与将IndexOutOfBoundsException 抛出List.get() 的越界参数的逻辑相同——更具体,错误往往因特定原因而出现。 @DavidMoles 我的问题是NullPointerExceptionIllegalArgumentException更具体。对我来说,NPE 的意思是“空值发生了一些不好的事情”,这似乎不如“传递了一个无效的参数”那么具体。此外,我想大多数人会立即认为 NPE 是“一个空指针被取消引用”。【参考方案3】:

我不知道。我只是通过简洁的调用来实现你想要的行为,模仿 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 一起使用。

【讨论】:

【参考方案4】:

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

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

考虑到上述情况,我想说在public void call(Person person) 示例中进行断言的最佳选择就像曼队长建议的那样:

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

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

所以最终的答案是 - 没有这样的 nice 库,不应该这样,因为这会令人困惑。 (并且应该更正 Spring Assert)。

【讨论】:

【参考方案5】:

由于这个问题的主题演变为“IllegalArgumentException和NullpointerException的正确用法”,我想指出Effective Java Item 60(第二版)中的直截了当的答案:

可以说,所有错误的方法调用都归结为非法参数 或非法状态,但其他例外通常用于某些类型的非法 论点和状态。 如果调用者在某个参数中传递了 null 值 被禁止,约定规定抛出 NullPointerException 而不是 IllegalArgumentException。同样,如果调用者传递了一个超出范围的 表示序列索引的参数中的值,IndexOutOfBoundsException 应该抛出而不是 IllegalArgumentException。

【讨论】:

【参考方案6】:

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

import static org.valid4j.Validation.*;

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

链接:

http://www.valid4j.org/ https://github.com/valid4j/valid4j

附带说明:这个库还支持前置条件和后置条件(就像断言一样),如果需要,可以注册您自己的自定义全局策略:

import static org.valid4j.Assertive.*;

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

以上是关于为 notNull 检查抛出 IllegalArgumentException 的前提条件库的主要内容,如果未能解决你的问题,请参考以下文章

Junit / Fongo:如何在单元测试中使用Fongo来检查NotNull

spring源码之—Assert.notNull

尽管有ReSharper [NotNull]注释,我应该添加显式空值检查吗?

spring方法验证中如何验证方法参数默认为NotNull?

工具使用冷知识

在 Python 中,如何检查驱动器是不是存在而不会为可移动驱动器抛出错误?