【问题标题】:Why doesn't Google Guava Preconditions's checkArgument return a value?为什么 Google Guava Preconditions 的 checkArgument 不返回值?
【发布时间】:2024-01-12 05:12:02
【问题描述】:

我真的很喜欢 guava 库允许简单的单行代码检查 null 的方式:

public void methodWithNullCheck(String couldBeNull) {
    String definitelyNotNull = checkNotNull(couldBeNull);
    //...
}

遗憾的是,对于简单的参数检查,您至少需要两行代码:

public void methodWithArgCheck(String couldBeEmpty) {
    checkArgument(!couldBeEmpty.isEmpty());
    String definitelyNotEmpty = couldBeEmpty;
    //...
}

但是可以添加可以进行参数检查的方法并且如果检查成功则返回一个值。以下是检查的示例以及如何实施:

public void methodWithEnhancedArgCheck(String couldBeEmpty) {
    String definitelyNotEmpty = EnhancedPreconditions.checkArgument(couldBeEmpty, !couldBeEmpty.isEmpty());
    //...
}

static class EnhancedPreconditions {
    public static <T> T checkArgument(T reference, boolean expression) {
        if (!expression) {
            throw new IllegalArgumentException();
        }

        return reference;
    }
}

我只是想知道这是设计使然,是否值得为此提出功能请求。

编辑:@Nizet,是的,检查方法可能很笨拙。但是,在构造函数中检查 null 看起来非常好,并且可以节省大量用于调试 NPE 的时间:

public class SomeClassWithDependency {

    private final SomeDependency someDependency;

    public SomeClassWithDependency(SomeDependency someDependency) {
        this.someDependency = checkNotNull(someDependency);
    }

    //...

编辑:接受 Nizet 的回答,因为我同意他的副作用和一致性推理。 此外,如果您查看 Xaerxess 评论,它看起来也会在其他开发人员中引起混淆。

【问题讨论】:

  • 除了您使用checkArgument(T ref, boolean expr) 的示例外,最近还有一个issue #1038 讨论Preconditions.checkArgument(T ref, Predicate&lt;T&gt; test) 被拒绝。您始终可以使用自己的方法创建 Preconditions2 类。

标签: java guava argument-passing


【解决方案1】:

checkNotNull 返回其参数的最大单一原因是它可以在构造函数中使用,如下所示:

public Foo(Bar bar) {
  this.bar = checkNotNull(bar);
}

但是checkArgument 没有做类似事情的主要原因是无论如何你都必须单独传递参数,这似乎不值得——尤其是在更复杂的前置条件检查中,它可以有时在自己的行上更具可读性。仅仅因为某些东西可以是单行的,并不意味着它应该是,如果它不增加可读性的话。

【讨论】:

  • 我不知道它确实返回了参数...进行“代码高尔夫”重构:-)
【解决方案2】:

我一直不明白为什么checkNotNull() 首先返回它的参数:

public void foo(String bar) {
    Preconditions.checkNotNull(bar);
    // here, you're sure that bar is not null. 
    // No need to use another variable or to reassign bar to the result 
    // of checkNotNull()
}

我个人忽略checkNotNull()的结果,如上。这使得事情与返回 void 的其他检查一致。

我看到的唯一优势是您可以执行类似的操作,但我发现它的可读性不如在两个单独的行中执行:

public String trim(String bar) {
    return Preconditions.checkNotNull(bar).trim();
}

所以,简而言之,我同意你的观点,API 有点不一致,但我希望所有方法都返回 void。一个方法应该要么有副作用,要么返回一些东西,但通常应该避免两者都做。在这里,该方法的目标是产生副作用:抛出异常。

编辑:

您的示例确实是对为什么返回参数有用的更有效的解释。但我仍然倾向于一致性和简洁性,而不是这种在一行中检查和分配的可能性。

【讨论】:

  • 我不会做像Preconditions.checkNotNull(bar).trim() 这样的事情,而不是简单的bar.trim()。在这两种情况下,您都会得到相同的 NPE,因此测试不会给您带来任何收益,您只会掩盖一件简单的事情。 checkNotNull 旨在快速失败,因此您可以避免可能不正确的部分执行方法并获得更短的堆栈跟踪。
  • 如果你把它写成:return checkNotNull(bar, "bar may not be null").trim();,那么它就开始有意义了。无论如何,我并不是说应该这样做。相反:我认为checkNotNull() 应该返回 void。
  • @JBNizet 我尊重你论证的质量,这就是我不会投反对票的原因。在我看来,checknotNull 方法是返回变量的一个非常好的选择,我希望有更多这样的方法(例如someInt / Ints.checkNoneZero(someOtherInt)。我认为这是一个很好的方法来突出重要的代码要求,而不会使方法不必要地长。我认为它使代码更具可读性,而不是更少。相反,在您的第一个示例中,我根本没有看到使用该方法的意义。
  • 我有 1 个用例,其中 checkNotNull() 返回其参数。对超类构造函数的调用:super(Preconditions.checkNotNull(param1))
【解决方案3】:

您可以将 valid4j 与 hamcrest-matchers 一起使用(在 Maven Central 上以 org.valid4j:valid4j 的形式找到)

对于前置条件和后置条件:

import static org.valid4j.Assertive.*;

this.myField = require(argument, notNullValue());
this.myInteger = require(x, greaterThan(0));
...
return ensure(result, isValid());

对于输入验证:

import static org.valid4j.Validation.*;


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

链接:

【讨论】: