【问题标题】:Should I write null checks for all of my methods?我应该为我的所有方法编写空检查吗?
【发布时间】:2020-04-16 22:52:07
【问题描述】:

我正在创建自己的库,例如 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() 方法的其他方法中编写相同的检查,还是只在该方法抛出相应异常的 cmets 中编写。

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() 方法。

最佳做法是什么?

【问题讨论】:

    标签: java nullpointerexception throw


    【解决方案1】:

    1.: 有函数Objects.requireNonNull,正是为此目的。如果给定的参数等于null,则会引发异常。

    if (messageDigest == null)
            throw new IllegalArgumentException("messageDigest cannot be null");
    

    应该只是

    Objects.requireNonNull(messageDigest, "messageDigest cannot be null");
    

    2.:由于null 而引发异常的最佳做法是将其null 最初不应该出现的地方引发。这就是Fail Fast Principle背后的动机。

    null 引用出现在它不应该的位置时,您想在恰好那个位置抛出异常或处理它。您想将null 引用传递给其他方法/函数。否则,当传递的null 引用导致程序出现问题时,您将很难调试代码。这是因为不清楚null 引用最初出现在哪里。但实际上导致的是你想要修复/处理的。一个例子:

    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 检查放在@ 的位置就足够了987654342@ 首先不应该出现,即a。该程序将快速失败

    null 签入c 会更糟糕,因为从逻辑上讲,c 不应该收到来自bnull 引用,而b 不应该收到来自a 的引用.如果null 引用导致c 出现问题,您将很难找出null 是否出现在ba 或调用a 的代码中。你知道,因为你想解决null引用的问题,不是由它引起的问题。当然,有时您无法修复事件本身,但您可以尝试尽可能接近事件并减少它造成的附带“伤害”。

    没有真正的 goto 方式来放置 null 支票。您必须将它们放置在正确的位置,以使程序在出现无效对象时快速失败null 检查的最佳位置由您决定,因为这完全取决于具体情况。

    我希望这能让您对如何处理这个问题有一个好主意。

    【讨论】:

    • 你对这篇文章的 JavaDoc 有什么看法:link@akuzminykh
    • @user11611653 视情况而定。 IllegalArgumentException 声明参数无效。这涉及各种参数,即不仅仅是null 引用。当String 有错误的模式时,你抛出IllegalArgumentException,当int 是负数而它应该是正数时等等。 NullPointerException 是由取消引用 null 引用引起的,所以它与 null 有关,仅此而已。如果您的代码可能因为null 而引发异常,请使用NullPointerException,否则使用IllegalArgumentExceptionfail fast 原则是最重要的。
    【解决方案2】:

    更好的做法是让update() 方法完成它的工作,如果它抛出一个Exception,你也会抛出一个异常。有两件常见的事情要做,都扔Exception,你也会得到一个。一种将throws SomeException 放在方法名称中的方法,其中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.
        }
    }
    

    这允许您在发现异常时为所欲为。

    【讨论】:

    • 我还有一个调用digest()方法的方法。例如,digestHex() 调用 digest()。而digest() 调用update()。我应该使用相同的做法吗? @Higigig
    • 是的,你应该这样做。但是,您不需要对 IllegalArgumentException 做任何事情,因为正如我之前所说,它是 RunTimeException 并自动抛出。
    【解决方案3】:
    1. 从 JDK15 开始(或者它已经在 14 中?),系统本身抛出的 NPE(即当你运行时,比如说,Object o = null; o.hashCode(); 将拥有所有细节;特别是表达式的文本。这意味着如果在您的代码中 NPE 会“自然”发生(因为您取消引用变量,或者将其传递给始终执行此操作的另一个方法),则无需进行 null 检查。从风格上讲,问题在于如果稍后如果您更改代码使其不必取消引用,您将失去调用该方法将引发空参数的效果。请记住一些事情。

    2. 根据我的经验,更常见的是显式抛出“NullPointerException”,即写入if (param == null) throw new NullPointerException("param")。自动抛出此问题的各种库通常也默认使用 NPE。

    3. 您不需要编写 if。 java 核心附带一个实用程序来单行它:Objects.requireNonNull(param, "param"); 比你的 3 行 if 块短很多!

    4. 您可以使用lombok's @NonNull 自动生成这些空检查,从而节省您更多的输入时间。

    5. 您可以将 IDE 配置为在更改参数值时发出警告;这意味着您不再需要在源文件中输入关键字“final”15,950,951(近似值)次。为您节省大量的打字时间。

    6. 虽然到目前为止这个问题的另一个答案说您不需要 声明 throws IllegalArgumentException,这是真的,但出于文档目的这样做是(再次,我的经验) 常见。

    7. 有注释来标记非空值。除了 lombok 之外,还有各种库可以为您提供编译时空值检查,并且它们通常内置在主要 IDE 中,或者有一个插件用于它们,因此您会在编写时遇到错误传递(潜在的)空值到不希望它的方法,或者进行无用的空检查(例如,对从表明它们从不返回空的方法返回的值进行空检查)。如果您可以依靠库的所有用户来使用此类工具,那么您根本不需要运行时空检查,但我建议您保留它们;我认为你不能依赖你的用户来拥有这些工具。不过,请考虑对您的参数进行注释,以便那些确实受益于编译时检查的人。

    对于编译时注解,您有很多选择(不幸的是):checkerframework 是迄今为止最广泛的,让您比编译时空检查保护走得更远。还有内置到您的 IDE 集:eclipse's 和 intellij's 是最常见的,但还有更多。

    只有 lombok 会为您生成运行时空检查,并且可以将 IDE 配置为在编译时分析中考虑它,为您提供两全其美的体验。或者,将这些框架的注释之一与Objects.requireNonNull 结合起来。

    【讨论】:

    • 你对这篇文章的 JavaDoc 有什么看法:link@rzwitserloot
    • 鉴于 Objects.requireNonNull 抛出 NPE,并且(尤其是 JDK15+ 更好的 NPE 消息)许多方法并没有使其明确,因此固有地抛出 NPE,你无法绕过传递的事实null 到你不应该这样做的地方的方法 WILL 肯定会抛出 NPE。所以,靠它:所有代码都应该抛出这个。在这方面将世界一分为二是没有意义的,IAE 可以被消除,而消除 NPE 似乎是不可能的。任何进一步的争论都归结为风格选择,而那些让实用主义退居二线,不是吗?
    猜你喜欢
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 2013-01-05
    • 2014-10-01
    • 1970-01-01
    • 2012-12-13
    • 2010-10-24
    • 2011-05-22
    相关资源
    最近更新 更多