为 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】:Preconditions
的checkArgument
呢?
public void call(Person person)
Preconditions.checkArgument(person.getPhone() != null);
// cally things...
checkArgument
throws
IllegalArgumentException
而不是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 我的问题是NullPointerException
不比IllegalArgumentException
更具体。对我来说,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
尽管有ReSharper [NotNull]注释,我应该添加显式空值检查吗?