您应该在生产代码中使用 assert 语句断言 not null 吗?

Big*_*d_E 33 java assert nullable

我看过这个问题,但还有几个关于assert关键字用法的问题。我正在与其他一些编码人员讨论如何使用assert. 对于此用例,有一种方法可以在满足某些先决条件时返回 null。我编写的代码调用该方法,然后断言它不返回 null,并继续使用返回的对象。

例子:

class CustomObject {
    private Object object;

    @Nullable
    public Object getObject() {
        return (object == null) ? generateObject() : object;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在想象我像这样使用它:

public void useObject(CustomObject customObject) {
    object = customObject.getObject();
    assert object != null;
    // Do stuff using object, which would throw a NPE if object is null.
}
Run Code Online (Sandbox Code Playgroud)

有人告诉我应该删除assert,它们应该用于生产代码,只能用于测试。真的吗?

aku*_*ykh 24

Objects.requireNonNull(Object)为此使用。

检查指定的对象引用是否为空。此方法主要用于在方法和构造函数中进行参数验证,[...]

在你的情况下,这将是:

public void useObject(CustomObject customObject) {
    object = customObject.getObject();
    Objects.requireNonNull(object); // throws NPE if object is null
    // do stuff with object
}
Run Code Online (Sandbox Code Playgroud)

这个功能是为你想做的事情而设计的:明确标记什么不是null。好处是您可以null在不应该出现的地方找到-values。您将减少调试nulls引起的问题的麻烦,这些 s 被传递到它们不应该存在的地方。

assert. Whileassert是用于检查布尔值的关键字,Objects.requireNonNull(Object)是一个函数,可以更容易地嵌入代码中。

Foo foo = Objects.requireNonNull(service.fetchFoo());

// you cannot write it in one line.
Bar bar = service.fetchBar();
assert bar != null;
Run Code Online (Sandbox Code Playgroud)
service.foo(Objects.requireNonNull(service.getBar()));

// you cannot write it in one line.
Bar bar = service.getBar();
assert bar != null;
service.foo(bar);
Run Code Online (Sandbox Code Playgroud)

请记住,这Objects.requireNonNull(Object)仅用于null-checking 而assert用于一般断言。所以assert不同的目的:主要是测试。它必须启用,因此您可以启用它进行测试并在生产中禁用它。使用它来将仅测试的测试与测试分开,或者更确切地说是检查,这也适用于生产代码。

  • @PeteKirkham 你是对的,*控制* 是错误的词。我更多地谈论语法的灵活性。另外:[此处](/sf/ask/3194304431/?noredirect=1&lq=1) 可以找到您希望代码抛出 NPE 的原因。 (2认同)

jep*_*bio 23

关于断言要记住的最重要的事情是它们可以被禁用,所以永远不要假设它们会被执行。

为了向后兼容,JVM 默认禁用断言验证。它们必须使用 -enableassertions 命令行参数或其简写 -ea 显式启用:

java -ea com.whatever.assertion.Assertion
Run Code Online (Sandbox Code Playgroud)

因此,依赖它们并不是一个好习惯。

由于默认情况下不启用断言,因此您永远不能假设它们在代码中使用时会被执行。所以你应该总是检查空值和空的 Optionals,避免使用断言来检查公共方法的输入,而是使用未经检查的异常......一般来说,所有检查都好像断言不存在一样。

  • @Big_Bad_E 断言是一种调试工具,它的存在是为了让程序尽可能早地、尽可能地失败。然后,测试套件的工作就是确保程序不会失败*尽管有断言*。这个想法是,一旦测试套件(它有 100% 的覆盖率,不是吗?!?)执行没有失败,就可以删除断言,因为它们无论如何都无法触发。当然,这个推理有一点理想主义,但这就是想法。 (3认同)

小智 11

当然,你被告知的是一个公然的谎言。这是为什么。

如果您只执行独立的 jvm,则默认情况下会禁用断言。当它们被禁用时,它们的占用空间为零,因此它们不会影响您的生产应用程序。但是,在开发和测试代码时,它们可能是您最好的朋友,并且大多数测试框架运行器都启用断言(JUnit 确实如此),因此您在运行单元测试时会执行断言代码,从而帮助您更早地检测任何潜在的错误(例如您可以为一些业务逻辑边界检查添加断言,这将有助于检测一些使用不适当值的代码)。

也就是说,正如另一个答案所暗示的那样,正是出于这个原因(它们并不总是启用),您不能依靠断言来进行一些重要检查,或者(尤其是!)维护任何状态。

有关如何使用断言的有趣示例,请查看此处- 在文件末尾有一个方法singleThreadedAccess(),该方法从第 201 行的断言语句中调用,用于捕获测试中任何潜在的多线程访问。


Chr*_*der 5

其他答案已经很好地涵盖了这一点,但还有其他选择。

例如,Spring有一个静态方法:

org.springframework.util.Assert.notNull(obj)

还有其他图书馆有自己的Assert.something()方法。自己编写也非常简单。

但是,请记住如果这是 Web 服务,您会抛出哪些异常。例如,前面提到的方法会抛出一个IllegalArgumentException异常,在 Spring 中默认返回一个 500。

对于 Web 服务,这通常不是内部服务器错误,也不应该是 500,而应该是 400,这是一个错误的请求。