我应该为我的所有方法编写空检查吗?
Should I write null checks for all of my methods?
我正在创建自己的库,例如 Apache Commons DigestUtils
以供学习之用。
在我的 update()
方法中,我写了一个简单的 if (something == null) throw new Exception
作为 null
检查。
/**
* Performs a digest update for the {@code String} (converted to bytes using the
* UTF-8 standard charsets) with a specific {@code MessageDigest} and returns
* the final digest data.
*
*
* @param messageDigest the {@code MessageDigest} with a specific algorithm to
* process the digest.
*
* @param data the data to digest.
*
* @return the {@code MessageDigest} with the processed update for the digest.
*
* @throws IllegalArgumentException if {@code messageDigest} is {@code null}.
*
* @throws IllegalArgumentException if {@code data} is {@code null}.
*/
public static MessageDigest update(final MessageDigest messageDigest, final String data) {
if (messageDigest == null)
throw new IllegalArgumentException("messageDigest cannot be null");
if (data == null)
throw new IllegalArgumentException("data cannot be null");
messageDigest.update(data.getBytes(StandardCharsets.UTF_8));
return messageDigest;
}
现在我不确定是否应该在其他必须调用update()
方法的方法中写相同的检查,或者只在注释中写该方法抛出相应的异常。
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) {
return update(messageDigest, data).digest();
}
或
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) {
if (messageDigest == null)
throw new IllegalArgumentException("messageDigest cannot be null");
if (data == null)
throw new IllegalArgumentException("data cannot be null");
return update(messageDigest, data).digest();
}
注意 digest()
方法调用 update()
方法。
最佳做法是什么?
最好的做法是让 update()
方法执行它的操作,如果它抛出 Exception
,您也会抛出异常。有两个常见的事情要做,都抛出 Exception
你也得到一个。在 SomeException
所在的方法名称中放置 throws SomeException
的一种方法是您要排除的 Exception
。
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) throws IllegalArgumentException {
return update(messageDigest, data).digest();
}
所以如果你从update()
得到一个Exception
,它会自动抛出它。注意:您不需要抛出 IllegalArgumentException
,因为它们是 RunTimeException
的一种类型,会自动抛出。第二种方法是 try catch,它给了你更多的选择。
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) throws IllegalArgumentException {
try {
return update(messageDigest, data).digest();
} catch(IllegalArgumentException e) {
// Do whatever you want. You can even throw e if you want.
}
}
这允许您在发现异常时为所欲为。
从 JDK15 开始(或者它已经在 14 中了吗?),系统本身抛出的 NPE(即当你 运行,比如说,Object o = null; o.hashCode();
将拥有所有细节;特别是表达式的文本。这意味着如果在您的代码中会出现 NPE 'naturally'(因为您取消引用该变量,或将其传递给另一个始终这样做的方法),则不需要空检查。从风格上讲,问题是如果稍后您更改代码以使其不一定取消引用,您将失去调用该方法将抛出空参数的效果。需要记住的事情。
根据我的经验,更常见的是显式抛出 'NullPointerException',即写成 if (param == null) throw new NullPointerException("param")
。自动抛出这个的各种库通常也默认为 NPE。
你不需要写if。 java 核心附带了一个单行实用程序:Objects.requireNonNull(param, "param");
比你的 3 行 if
块短很多!
您可以使用 lombok's @NonNull
自动生成这些空检查,节省您更多的输入。
您可以将 IDE 配置为在更改参数值时发出警告;这意味着您不再需要在源文件中放置关键字 'final' 15,950,951(近似)次。节省您很多的打字时间。
虽然到目前为止这个问题的另一个答案说你不需要需要声明throws IllegalArgumentException
,这是真的,这样做出于文档目的(同样,我的经验)很常见。
有标注非无效的注解。除了 lombok 之外,还有各种库可以为您提供编译时空值检查,它们通常内置于主要的 IDE 中,或者有一个插件,因此您会在编写时遇到错误您正在将(潜在的)空值传递给不需要它的方法,或者进行无用的空值检查(例如值 returned 来自表明它们从不 return null 的方法的空值检查)。如果您可以依靠图书馆的所有用户使用此类工具,那么您根本不需要 运行time nullchecks,但我建议您保留它们;我认为你不能指望你的用户拥有这些工具。不过,请考虑注释您的参数,以便那些确实受益于编译时检查的人。
对于编译时注释,这里有多种选择(不幸的是):checkerframework which is by far the most expansive, letting you go a lot further than compile-time null check guards. There's also the baked-into-your-IDE sets: eclipse's and intellij 是最常见的,但还有更多。
只有 lombok 会为你生成一个运行time nullcheck,并且IDEs 也可以配置为考虑将其用于编译时分析,让您两全其美。或者,将这些框架的注释之一与 Objects.requireNonNull
.
结合起来
1.: 有函数Objects.requireNonNull
,就是为了这个目的。如果给定的参数等于 null
.
,它会抛出异常
if (messageDigest == null)
throw new IllegalArgumentException("messageDigest cannot be null");
应该只是
Objects.requireNonNull(messageDigest, "messageDigest cannot be null");
2.: 由于 null
而抛出异常的最佳实践是将其抛出 在 null
不应该的地方t 出现在第一位 。这就是 Fail Fast Principle.
背后的动机
当 null
引用出现在它 不应该 的位置时,您想在 恰好 那个地方。您不想 将null
引用传递给其他methods/functions。否则,当传递的 null
引用导致您的程序出现问题时,您将很难调试代码。这是因为不清楚 null
引用首先出现在哪里;但实际上 造成的 正是你想要的 fix/handle。一个例子:
public class Demo {
public void a(Object obj) {
b(obj);
}
private void b(Object obj) {
c(obj);
}
private void c(Object obj) {
// Calculation using obj.
}
}
如果很明显 a
永远不应该传递 null
引用,那么这是检查它的正确位置。
public void a(Object obj) {
Objects.requireNonNull(obj); // Throw exception when null.
b(obj);
}
在每个方法中都进行 null
检查是很糟糕的,因为 obj
不应该 null
这一事实对所有这些方法都很重要,因此足以放置null
首先检查 null
不应出现的位置,即 a
。该程序应快速失败。
将 null
检查放在 c
中会更糟糕,因为 c
逻辑上应该永远不会收到来自 b
的 null
引用,并且b
不应该从 a
收到一个。如果 null
引用导致 c
出现问题,您将很难确定 null
是否出现在 b
或 a
或调用 a
。您想要 知道,因为您想要修复发生的 null
引用的问题,而不是 它引起的问题。当然,有时候事件本身并不能解决问题,但是可以尽量接近事件发生,减少它造成的附带"damage"
没有真正的 goto 方法来放置 null
检查。您必须将它们放在正确的位置,以使程序在出现无效对象时 快速失败 。 null
检查的最佳位置由您决定,因为这完全取决于具体情况。
我希望这能让您对如何处理这个问题有个好主意。
我正在创建自己的库,例如 Apache Commons DigestUtils
以供学习之用。
在我的 update()
方法中,我写了一个简单的 if (something == null) throw new Exception
作为 null
检查。
/**
* Performs a digest update for the {@code String} (converted to bytes using the
* UTF-8 standard charsets) with a specific {@code MessageDigest} and returns
* the final digest data.
*
*
* @param messageDigest the {@code MessageDigest} with a specific algorithm to
* process the digest.
*
* @param data the data to digest.
*
* @return the {@code MessageDigest} with the processed update for the digest.
*
* @throws IllegalArgumentException if {@code messageDigest} is {@code null}.
*
* @throws IllegalArgumentException if {@code data} is {@code null}.
*/
public static MessageDigest update(final MessageDigest messageDigest, final String data) {
if (messageDigest == null)
throw new IllegalArgumentException("messageDigest cannot be null");
if (data == null)
throw new IllegalArgumentException("data cannot be null");
messageDigest.update(data.getBytes(StandardCharsets.UTF_8));
return messageDigest;
}
现在我不确定是否应该在其他必须调用update()
方法的方法中写相同的检查,或者只在注释中写该方法抛出相应的异常。
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) {
return update(messageDigest, data).digest();
}
或
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) {
if (messageDigest == null)
throw new IllegalArgumentException("messageDigest cannot be null");
if (data == null)
throw new IllegalArgumentException("data cannot be null");
return update(messageDigest, data).digest();
}
注意 digest()
方法调用 update()
方法。
最佳做法是什么?
最好的做法是让 update()
方法执行它的操作,如果它抛出 Exception
,您也会抛出异常。有两个常见的事情要做,都抛出 Exception
你也得到一个。在 SomeException
所在的方法名称中放置 throws SomeException
的一种方法是您要排除的 Exception
。
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) throws IllegalArgumentException {
return update(messageDigest, data).digest();
}
所以如果你从update()
得到一个Exception
,它会自动抛出它。注意:您不需要抛出 IllegalArgumentException
,因为它们是 RunTimeException
的一种类型,会自动抛出。第二种方法是 try catch,它给了你更多的选择。
public static byte[] digest(final MessageDigest messageDigest, final byte[] data) throws IllegalArgumentException {
try {
return update(messageDigest, data).digest();
} catch(IllegalArgumentException e) {
// Do whatever you want. You can even throw e if you want.
}
}
这允许您在发现异常时为所欲为。
从 JDK15 开始(或者它已经在 14 中了吗?),系统本身抛出的 NPE(即当你 运行,比如说,
Object o = null; o.hashCode();
将拥有所有细节;特别是表达式的文本。这意味着如果在您的代码中会出现 NPE 'naturally'(因为您取消引用该变量,或将其传递给另一个始终这样做的方法),则不需要空检查。从风格上讲,问题是如果稍后您更改代码以使其不一定取消引用,您将失去调用该方法将抛出空参数的效果。需要记住的事情。根据我的经验,更常见的是显式抛出 'NullPointerException',即写成
if (param == null) throw new NullPointerException("param")
。自动抛出这个的各种库通常也默认为 NPE。你不需要写if。 java 核心附带了一个单行实用程序:
Objects.requireNonNull(param, "param");
比你的 3 行if
块短很多!您可以使用 lombok's
@NonNull
自动生成这些空检查,节省您更多的输入。您可以将 IDE 配置为在更改参数值时发出警告;这意味着您不再需要在源文件中放置关键字 'final' 15,950,951(近似)次。节省您很多的打字时间。
虽然到目前为止这个问题的另一个答案说你不需要需要声明
throws IllegalArgumentException
,这是真的,这样做出于文档目的(同样,我的经验)很常见。有标注非无效的注解。除了 lombok 之外,还有各种库可以为您提供编译时空值检查,它们通常内置于主要的 IDE 中,或者有一个插件,因此您会在编写时遇到错误您正在将(潜在的)空值传递给不需要它的方法,或者进行无用的空值检查(例如值 returned 来自表明它们从不 return null 的方法的空值检查)。如果您可以依靠图书馆的所有用户使用此类工具,那么您根本不需要 运行time nullchecks,但我建议您保留它们;我认为你不能指望你的用户拥有这些工具。不过,请考虑注释您的参数,以便那些确实受益于编译时检查的人。
对于编译时注释,这里有多种选择(不幸的是):checkerframework which is by far the most expansive, letting you go a lot further than compile-time null check guards. There's also the baked-into-your-IDE sets: eclipse's and intellij 是最常见的,但还有更多。
只有 lombok 会为你生成一个运行time nullcheck,并且IDEs 也可以配置为考虑将其用于编译时分析,让您两全其美。或者,将这些框架的注释之一与 Objects.requireNonNull
.
1.: 有函数Objects.requireNonNull
,就是为了这个目的。如果给定的参数等于 null
.
if (messageDigest == null)
throw new IllegalArgumentException("messageDigest cannot be null");
应该只是
Objects.requireNonNull(messageDigest, "messageDigest cannot be null");
2.: 由于 null
而抛出异常的最佳实践是将其抛出 在 null
不应该的地方t 出现在第一位 。这就是 Fail Fast Principle.
当 null
引用出现在它 不应该 的位置时,您想在 恰好 那个地方。您不想 将null
引用传递给其他methods/functions。否则,当传递的 null
引用导致您的程序出现问题时,您将很难调试代码。这是因为不清楚 null
引用首先出现在哪里;但实际上 造成的 正是你想要的 fix/handle。一个例子:
public class Demo {
public void a(Object obj) {
b(obj);
}
private void b(Object obj) {
c(obj);
}
private void c(Object obj) {
// Calculation using obj.
}
}
如果很明显 a
永远不应该传递 null
引用,那么这是检查它的正确位置。
public void a(Object obj) {
Objects.requireNonNull(obj); // Throw exception when null.
b(obj);
}
在每个方法中都进行 null
检查是很糟糕的,因为 obj
不应该 null
这一事实对所有这些方法都很重要,因此足以放置null
首先检查 null
不应出现的位置,即 a
。该程序应快速失败。
将 null
检查放在 c
中会更糟糕,因为 c
逻辑上应该永远不会收到来自 b
的 null
引用,并且b
不应该从 a
收到一个。如果 null
引用导致 c
出现问题,您将很难确定 null
是否出现在 b
或 a
或调用 a
。您想要 知道,因为您想要修复发生的 null
引用的问题,而不是 它引起的问题。当然,有时候事件本身并不能解决问题,但是可以尽量接近事件发生,减少它造成的附带"damage"
没有真正的 goto 方法来放置 null
检查。您必须将它们放在正确的位置,以使程序在出现无效对象时 快速失败 。 null
检查的最佳位置由您决定,因为这完全取决于具体情况。
我希望这能让您对如何处理这个问题有个好主意。