空值检查错误信息为“is null”或“was null”。

11

在Java代码中进行空值检查时,如果对于null值抛出IllegalArgumentExceptions异常,你通常会使用什么样的消息模板?

我们倾向于使用这种形式

public User getUser(String username){
   if (username == null){
     throw new IllegalArgumentException("username is null");   
   }
   // ...
}

"is null"和"was null"哪个更好,为什么?

我认为"is null"更自然。


2
这就是为什么我喜欢在C++中使用const引用。 - Viktor Sehr
2
@Viktor Sehr 啊哈。所以在C++中喜欢使用const引用的原因是为了避免“is”与“was”的争论。 - Tom Hawtin - tackline
5
我有点觉得“is”和“was”的问题重要到让人们提出疑问,这让我有些好笑。我赞赏你想写出尽可能好的代码,但是恕我直言,也许现在是重新审视哪些代码质量问题真正重要的时候了。 - Kevin Bourrillion
@kevin-bourrillion,当然我知道还有更重要的事情,我只是想听听大家对这个问题的看法。“也许现在是重新审视哪些代码质量问题真正重要的时候了。”,这将是我的下一个问题 ;) - Timo Westkämper
4个回答

15

由于抛出了一个失败的前置条件检查的异常,我认为你应该陈述被违反的要求,而不仅仅是陈述一个事实。

也就是说,不要说 "username is null",而是说"username should not be null"


关于使用库进行前置条件检查

作为一个提示,您可以使用许多旨在简化前置条件检查的库之一。Guava中的许多代码都使用com.google.common.base.Preconditions

Simple static methods to be called at the start of your own methods to verify correct arguments and state. This allows constructs such as

 if (count <= 0) {
   throw new IllegalArgumentException("must be positive: " + count);
 }

to be replaced with the more compact

 checkArgument(count > 0, "must be positive: %s", count);

在这里更直接相关的是它具有checkNotNull,这使得您可以简单地编写:

  checkNotNull(username, "username should not be null");

请注意上述代码的自然语言表达,详细说明了违反的{{requirement}}。

叙述事实的替代方法更加尴尬:

 // Awkward!
 checkArgument(count > 0, "is negative or zero: %s", count);
 checkNotNull(username, "username is null");

此外,这也可能不太有用,因为客户端可能已经意识到这一事实,而异常并不能帮助他们弄清楚实际要求是什么。

关于IllegalArgumentExceptionNullPointerException

虽然您的原始代码在null参数上抛出 IllegalArgumentException,但Guava的Preconditions.checkNotNull会抛出NullPointerException

这符合API设置的指南:

NullPointerException:应用程序应该抛出此类的实例来指示null对象的其他非法用途。

此外,以下是《Effective Java第二版:条款60:倾向于使用标准异常》中的一句话:

可以说,所有错误的方法调用都归结为非法参数或非法状态,但是对于某些特定类型的非法参数和状态,标准上使用其他异常。如果调用者在禁止使用null值的某个参数中传递了null,则约定应抛出NullPointerException而不是IllegalArgumentException


@polygenelubricants,我不确定我是否同意关于异常类型问题的观点,但你回答的第一部分真的很好。谢谢! - Timo Westkämper
“惯例规定”并不足够,对于显式检查使用IllegalArgumentException,对于系统执行的操作使用NPE更有意义,但知道还有其他观点也是好的 ;) - Timo Westkämper
2
IAE与NPE关于空参数的争论是古老的。IAE有很多道理,但是NPE背后也有许多年的传统。对于Preconditions.checkNotNull,我们必须选择其中之一,并选择了NPE。NPE的一个优点是,如果您的方法从不显式检查(只是取消引用)到显式检查,或者反之,则异常类型不会更改。有些人认为即使在两行代码下抛出相同的异常,也应该始终彻底检查所有内容,但我认为这是浪费时间和空间。 - Kevin Bourrillion
1
(续)关于这一点,所有“彻底”和“全面”的工作都应该在单元测试中完成,而不是在实现代码中。 - Kevin Bourrillion
@Kevin,你能更详细地解释一下为什么你选择了NPE吗?我还想知道为什么要引入这些Preconditions.check*?是为了将检查和抛出异常的三行代码改为单行代码吗? - Thorbjørn Ravn Andersen
1
问题1:没有什么更多的解释了。正如我所说的,它背后有着“多年传统的重量”,我们的目的不是要试图反抗这种传统,而是要简化我们的用户已经在做的事情。问题2:是的,就是这样。 - Kevin Bourrillion

5

is null,因为参数仍然为空。

但是,为什么不直接抛出一个没有消息的NullPointerException呢?


3
抛出没有信息的 NullPointerException 不好,因为很难与意外的 NullPointerException 区分开来。 - Timo Westkämper
3
抛出一个没有信息的异常通常是一个不好的想法(如果不是总是)。仅有异常很少能提供足够的信息来理解实际出了什么问题。 - hudolejev
2
最好的做法是:在消息中抛出参数名称的 NPE 异常。不需要冗长但指定参数名称可以提供帮助信息。 - Konrad Rudolph
1
@Timo 这是一个 NPE(空指针异常)。当然是意外的!如果你认为添加参数名称很重要,那么你的方法显然有太多参数,你使用了太多可为空的值。 - Tom Hawtin - tackline
2
抛出一个没有消息的空指针异常意味着程序员必须仅根据行号查看源代码来确定异常的原因。作为程序员,你知道问题出在哪里,为什么不说出来呢? - Thorbjørn Ravn Andersen
显示剩余4条评论

0

我倾向于写成这样:

public User getUser(String username) {
   if (username.length() == 0) {
       throw new IllegalArgumentException("username is empty");   
   }
   // ...
}

这就一石二鸟了。首先,它检测到用户名为空字符串的情况,我假设这是一个错误(为了辩论而假设)。其次,如果参数为null,尝试分派length调用将会导致NullPointerException

记录一下,对于意外的null,抛出的预期异常是NullPointerException。如果你不使用它的主要原因是NPE通常没有消息,请按如下方式编写代码:

public User getUser(String username){
   if (username == null){
       throw new NullPointerException("username is null");   
   }
   if (username.length() == 0) {
       throw new IllegalArgumentException("username is empty");   
   }
   // ...
}

在这里为什么要使用 NPE(Null Pointer Exception)呢?因为 NPE 几乎总是指示了一种不同类型的问题,而不是其他类型的参数验证错误;例如,一个未初始化的字段或数组单元,或者一个没有正确处理的“可选”值。

最后来回答这个问题:

哪种表达方式更好:“is null” 还是 “was null”,为什么?

这是个人观点的问题,但我会写成 “is null”。

  • 因为该消息是在异常抛出时报告的状态。
  • 因为按照惯例应该这样做。

0
我建议说:
  if (userName == null) {
     throw new IllegalArgumentException("username == null");
   }

这是如此致命,以至于程序员必须无论如何查看它。在异常消息中引用有问题的代码片段是我能想象到的最简洁的方法。


有趣的方法。谢谢分享!但是请修复userName的大小写。(userName != username) - Timo Westkämper
希望这只是一个小细节 - 当然,除非您同时将username和userName作为参数。 :) - Thorbjørn Ravn Andersen
嗯,是的,但它看起来很混乱。但也许这也是我不知道的"最佳实践" ;) - Timo Westkämper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接