您是否应该在生产代码中使用assert语句断言不为null?[关闭]


32

我已经看到了这个问题,但是关于assert关键字的用法还有其他一些问题。我正在和其他一些编码员讨论使用assert。对于此用例,有一种方法可以在满足某些先决条件的情况下返回null。我编写的代码调用该方法,然后断言它不会返回null,并继续使用返回的对象。

例:

class CustomObject {
    private Object object;

    @Nullable
    public Object getObject() {
        return (object == null) ? generateObject() : object;
    }
}

现在想象一下我是这样使用它的:

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

有人告诉我应该删除assert不要在生产代码中使用它们,而只能在测试中使用。真的吗?


19
默认情况下,断言被禁用。您必须在运行时通过-ea或等效方式显式启用它们。
jarmod

有关软件工程的相关问题:softwareengineering.stackexchange.com/q/137158/187318(尽管它已经很老了,所以那里公认的答案仍然建议使用外部库,自Objects.requireNonNullJava 8 引入以来,该库不再需要)。
绿巨人

这个问题的标题过于宽泛,但是正文中提出的问题要狭窄得多,每个答案都由辅助功能完全处理。
smci

为了清楚起见,您在问客户端代码是否应在对象上实现非空检查/声明。(这与询问代码本身是否应保证对象不能为null或在其中断言不同)。
smci

Answers:


19

使用Objects.requireNonNull(Object)了点。

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

在您的情况下,这将是:

public void useObject(CustomObject customObject) {
    object = customObject.getObject();
    Objects.requireNonNull(object);
    // Do stuff using object, which would throw a NPE if object is null.
}

此功能用于您提到的目的,即显式标记不为null的内容;也正在生产中。这样做的最大好处是,您可以确保在不应该出现空值的地方找到空值。调试由空值传递到不应该传递的地方引起的问题时,您将遇到麻烦。

另一个好处是与相比,空检查的额外灵活性assert。虽然assert是用于检查布尔值的关键字,但它Objects.requireNonNull(Object)是一个函数,因此可以嵌入到代码中,更加灵活和易读。例如:

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

// You cannot write it in on line.
Bar bar = service.fetchBar();
assert bar != null;
service.foo(Objects.requireNonNull(service.getBar()));

// You cannot write it in on line.
Bar bar = service.getBar();
assert bar != null;
service.foo(bar);

请记住,这Objects.requireNonNull(Object)是专门用于对null进行assert广义检查的地方。assert在这方面的目的略有不同,即主要是测试。必须启用它,因此您可以启用它以进行测试并禁用它以进行生产。无论如何,请勿将其用于生产代码。它可能会使用于测试的不必要和复杂的验证减慢应用程序的速度。使用它也可以将仅测试检查与用于生产的检查分开。

查阅官方文档以获取有关的详细信息assert


14
谁和何时宣布断言是遗产?任何链接/参考?我无法想象有任何理智的开发人员定义“传统”工具来启用零占用空间验证,该验证可以在测试中轻松启用,而在生产中禁用。
德米特里·皮斯克洛夫

假设您可以在运行时确定assert是否运行,但是不能在运行时确定requireNonNull是否抛出NPE,那么“对它的控制比对assert的控制更多”是什么意思?
皮特·柯卡姆

@DmitryPisklov对,我已经编辑了。这是一个很好的测试工具。
akuzminykh

2
@PeteKirkham你是对的,控制是错误的说法。我更多是在谈论语法的灵活性。另外:可能是找到您为什么要代码抛出NPE的原因。
akuzminykh

1
“不要将其用于生产代码。它可能会使应用程序变慢”,可以,但是值得。Java具有内置的运行时检查功能,以确保您不会溢出数组。尽管可能会降低性能,但即使在生产部署中也可以启用这些功能。我们甚至都没有选择的余地。在生产代码中进行运行时检查并非总是错误的。
Max Barraclough

22

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

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

java -ea com.whatever.assertion.Assertion

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

由于默认情况下未启用断言,因此您永远无法假定在代码中使用它们时将执行它们。因此,您应始终检查null值和空的Optionals,避免使用断言来检查输入到公共方法中的输入,而应使用未经检查的异常...通常,所有检查都像断言不在那里一样。


依靠它们显然是错误的做法,但是通常使用它是否是错误的做法?
Big_Bad_E

3
@Big_Bad_E断言是一种调试工具,可以使程序尽早且尽可能地发声失败。然后,测试套件的工作就是确保即使有断言,程序也不会失败。这个想法是,一旦测试套件(覆盖率达到100%,不是吗?当然在这种推理中有一些理想主义,但这就是想法。
cmaster-恢复莫妮卡

11

当然,您被告知的是公然的谎言。这就是为什么。

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

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

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


4

其他答案已经足够涵盖了这一点,但是还有其他选择。

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

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

还有其他具有自己Assert.something()方法的库。自己编写也很简单。

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

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


1
如果“ Assert”方法没有立即使进程崩溃,而是抛出某种异常,则它不是断言。整个问题的关键assert在于,要立即崩溃生成的核心文件,以便您可以在违反条件的地方使用自己喜欢的调试器进行事后检查。
cmaster-恢复莫妮卡

断言不会立即使进程崩溃。他们抛出一个错误,可能会被捕获。未处理的异常将使测试以及错误崩溃。您可以根据需要争论语义。如果您愿意,可以编写一个引发错误而不是异常的自定义断言。实际上,在绝大多数情况下,适当的措施是引发异常而不是错误。
Christopher Schneider

啊,Java确实定义assert了抛出。有趣。C / C ++版本不执行此操作。它立即发出一个信号,即a)终止进程,b)创建一个核心转储。这样做是有原因的:调试这样的断言失败非常简单,因为您仍然可以获得有关调用堆栈的全部信息。定义assert抛出异常,然后可能会意外地以编程方式捕获(异常),这会破坏目的,恕我直言。
cmaster-恢复莫妮卡

正如在C和C ++中有一些(或许多)怪异的事物一样,在Java中也有一些。其中之一是Throwable。他们总是可以被抓住的。我不知道这是否是一件好事。我想这取决于。许多Java程序都是Web服务,由于几乎任何原因它都不会崩溃,因此几乎所有内容都会被捕获并记录下来。堆栈跟踪是很棒的事情之一,通常它足以诊断出异常或错误的原因。
Christopher Schneider

3

只要这样做,就可以使用Use断言来帮助捕获编程错误,即错误。

不要使用assert来捕获可能在逻辑上发生的事情,即格式错误的输入。仅当错误不可恢复时才使用断言。

检查断言时,请勿在运行的代码中放置任何生产逻辑。如果您的软件写得很好,这是微不足道的,但是如果不是,那么您可能会产生微妙的副作用,并且在启用和禁用断言的情况下可能会有不同的总体行为。

如果您的公司有“测试代码”和“生产代码”在做相同的事情,但是作为不同的代码库(或不同的编辑阶段),那就走了,再也不会回来。试图解决这种无能的问题可能会浪费您的时间。如果您的公司没有在测试代码之外放置任何assert语句,请告诉他们在生产版本中禁用了asserts,如果没有,则现在将解决该错误放在第一位。

asserts的值正好在业务逻辑中使用,而不仅仅是测试套件。这样就很容易进行许多高级测试,而不必显式测试很多事情来遍历代码的大块并触发所有这些断言。在我的一些项目中,典型的测试甚至没有真正断言任何东西,它们只是命令根据特定的输入进行计算,这导致检查了数百个断言,即使在很小的逻辑底层也发现了问题。


2

您可以随时使用assert。争论来自何时使用。例如在指南中

  • 不要在公共方法中使用断言进行参数检查。
  • 不要使用断言来执行应用程序正确操作所需的任何工作。

4
好的规则。但是由于某些原因,答案会更好。
cmaster-恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.