您应该在生产代码中使用 assert 语句断言 not null 吗?
Should you assert not null with the assert statement in production code?
我看到了 this 个问题,但还有一些关于 assert
关键字用法的问题。我正在与其他一些编码人员就使用 assert
进行辩论。对于这个用例,如果满足某些先决条件,有一个方法可以 return null。我编写的代码调用该方法,然后断言它不 return null,并继续使用 returned 对象。
示例:
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
,它们应该永远不会 用于生产代码,只能用于测试。是真的吗?
关于断言最重要的一点是它们可以被禁用,所以永远不要假设它们会被执行。
为了向后兼容,JVM 默认禁用断言验证。它们必须使用 -enableassertions 命令行参数或其 shorthand -ea:
显式启用
java -ea com.whatever.assertion.Assertion
因此,依赖它们并不是一个好习惯。
由于默认情况下未启用断言,因此您永远不能假设它们会在代码中使用时执行。因此,您应该始终检查空值和空 Optionals,避免使用断言检查 public 方法的输入,而是使用未经检查的异常......通常进行所有检查,就好像断言不存在一样。
为此使用 Objects.requireNonNull(Object)
。
Checks that the specified object reference is not null. This method is designed primarily for doing parameter validation in methods and constructors, [...]
在你的情况下是:
public void useObject(CustomObject customObject) {
object = customObject.getObject();
Objects.requireNonNull(object); // throws NPE if object is null
// do stuff with object
}
这个功能是为你想做的事情而设计的:明确标记不应该做的事情null
。好处是您可以在不应该出现的地方找到 null
-values。您将遇到 由 null
s 引起的问题,这些问题被传递到它们不应该出现的地方。
与 assert
相比,另一个好处是使用此功能时的灵活性。 assert
是一个用于检查布尔值的关键字,而 Objects.requireNonNull(Object)
是一个函数,可以更容易地嵌入到代码中。
Foo foo = Objects.requireNonNull(service.fetchFoo());
// you cannot write it in one line.
Bar bar = service.fetchBar();
assert bar != null;
service.foo(Objects.requireNonNull(service.getBar()));
// you cannot write it in one line.
Bar bar = service.getBar();
assert bar != null;
service.foo(bar);
请记住,Objects.requireNonNull(Object)
仅用于 null
检查,而 assert
用于一般断言。所以 assert
有 different purposes:主要是测试。它必须启用,因此您可以启用它进行测试并在生产中禁用它。使用它来将仅测试的测试与测试分开,或者更确切地说,检查也适用于生产代码。
你被告知的肯定是一个公然的谎言。原因如下。
如果您只执行独立的 jvm,默认情况下断言是禁用的。当它们被禁用时,它们的占用空间为零,因此它们不会影响您的生产应用程序。但是,在开发和测试您的代码时,它们可能是您最好的朋友,并且大多数测试框架 运行 人员都启用断言(JUnit 支持),因此当您 运行 单元测试时,您的断言代码将被执行,帮助您及早发现任何潜在的错误(例如,您可以为某些业务逻辑边界检查添加断言,这将有助于检测某些使用不适当值的代码)。
也就是说,正如其他答案所暗示的那样,正是出于这个原因(它们并不总是启用),您不能依赖断言来进行一些重要检查,或者(尤其是!)维持任何状态。
有关如何使用断言的有趣示例,请查看 here - 在文件末尾有一个方法 singleThreadedAccess()
从第 201 行的断言语句中调用,并且是否可以在测试中捕获任何潜在的多线程访问。
您可以随时使用断言。争论的焦点是何时使用。例如在 the guide 中:
- 不要在 public 方法中使用断言进行参数检查。
- 不要使用断言来完成您的应用程序正确运行所需的任何工作。
其他答案已经很好地涵盖了这一点,但还有其他选择。
例如Spring有一个静态方法:
org.springframework.util.Assert.notNull(obj)
还有其他库也有自己的 Assert.something()
方法。自己写也很简单。
但是,请记住,如果这是一个网络服务,您会抛出哪些异常。例如,前面提到的方法抛出一个 IllegalArgumentException
,默认情况下 Spring returns 一个 500。
对于 Web 服务,这通常 不是 内部服务器错误,不应该是 500,而是 400,这是一个错误的请求。
只要这样做有助于捕获编程错误,即错误,请大量使用断言。
不要使用断言来捕获逻辑上可能发生的事情,即格式错误的输入。仅当错误不可恢复时才使用断言。
不要在检查断言时运行的代码中放置任何生产逻辑。如果你的软件写得很好,这是微不足道的事实,但如果不是,那么你可能会产生微妙的副作用,以及启用和禁用断言时不同的整体行为。
如果您的公司有 "testing code" 和 "production code" 做同样的事情但作为不同的代码库(或不同的编辑阶段),离开那里,永远不要回来。试图解决这种水平的无能可能是在浪费你的时间。
如果您的公司没有在测试代码之外放置任何断言语句,请告诉他们断言在生产构建中被禁用,如果没有,那么修复该错误现在是您的首要任务。
断言的价值恰恰是要在业务逻辑内部使用,而不仅仅是测试套件。
这使得产生许多高级测试变得容易,这些测试不必显式测试很多东西来遍历大块代码并触发所有这些断言。
在我的一些项目中,典型的测试甚至没有真正断言任何东西,它们只是命令根据特定输入进行计算,这导致需要检查数百个断言,甚至在微小的逻辑片段中也会发现问题。
我看到了 this 个问题,但还有一些关于 assert
关键字用法的问题。我正在与其他一些编码人员就使用 assert
进行辩论。对于这个用例,如果满足某些先决条件,有一个方法可以 return null。我编写的代码调用该方法,然后断言它不 return null,并继续使用 returned 对象。
示例:
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
,它们应该永远不会 用于生产代码,只能用于测试。是真的吗?
关于断言最重要的一点是它们可以被禁用,所以永远不要假设它们会被执行。
为了向后兼容,JVM 默认禁用断言验证。它们必须使用 -enableassertions 命令行参数或其 shorthand -ea:
显式启用java -ea com.whatever.assertion.Assertion
因此,依赖它们并不是一个好习惯。
由于默认情况下未启用断言,因此您永远不能假设它们会在代码中使用时执行。因此,您应该始终检查空值和空 Optionals,避免使用断言检查 public 方法的输入,而是使用未经检查的异常......通常进行所有检查,就好像断言不存在一样。
为此使用 Objects.requireNonNull(Object)
。
Checks that the specified object reference is not null. This method is designed primarily for doing parameter validation in methods and constructors, [...]
在你的情况下是:
public void useObject(CustomObject customObject) {
object = customObject.getObject();
Objects.requireNonNull(object); // throws NPE if object is null
// do stuff with object
}
这个功能是为你想做的事情而设计的:明确标记不应该做的事情null
。好处是您可以在不应该出现的地方找到 null
-values。您将遇到 null
s 引起的问题,这些问题被传递到它们不应该出现的地方。
与 assert
相比,另一个好处是使用此功能时的灵活性。 assert
是一个用于检查布尔值的关键字,而 Objects.requireNonNull(Object)
是一个函数,可以更容易地嵌入到代码中。
Foo foo = Objects.requireNonNull(service.fetchFoo());
// you cannot write it in one line.
Bar bar = service.fetchBar();
assert bar != null;
service.foo(Objects.requireNonNull(service.getBar()));
// you cannot write it in one line.
Bar bar = service.getBar();
assert bar != null;
service.foo(bar);
请记住,Objects.requireNonNull(Object)
仅用于 null
检查,而 assert
用于一般断言。所以 assert
有 different purposes:主要是测试。它必须启用,因此您可以启用它进行测试并在生产中禁用它。使用它来将仅测试的测试与测试分开,或者更确切地说,检查也适用于生产代码。
你被告知的肯定是一个公然的谎言。原因如下。
如果您只执行独立的 jvm,默认情况下断言是禁用的。当它们被禁用时,它们的占用空间为零,因此它们不会影响您的生产应用程序。但是,在开发和测试您的代码时,它们可能是您最好的朋友,并且大多数测试框架 运行 人员都启用断言(JUnit 支持),因此当您 运行 单元测试时,您的断言代码将被执行,帮助您及早发现任何潜在的错误(例如,您可以为某些业务逻辑边界检查添加断言,这将有助于检测某些使用不适当值的代码)。
也就是说,正如其他答案所暗示的那样,正是出于这个原因(它们并不总是启用),您不能依赖断言来进行一些重要检查,或者(尤其是!)维持任何状态。
有关如何使用断言的有趣示例,请查看 here - 在文件末尾有一个方法 singleThreadedAccess()
从第 201 行的断言语句中调用,并且是否可以在测试中捕获任何潜在的多线程访问。
您可以随时使用断言。争论的焦点是何时使用。例如在 the guide 中:
- 不要在 public 方法中使用断言进行参数检查。
- 不要使用断言来完成您的应用程序正确运行所需的任何工作。
其他答案已经很好地涵盖了这一点,但还有其他选择。
例如Spring有一个静态方法:
org.springframework.util.Assert.notNull(obj)
还有其他库也有自己的 Assert.something()
方法。自己写也很简单。
但是,请记住,如果这是一个网络服务,您会抛出哪些异常。例如,前面提到的方法抛出一个 IllegalArgumentException
,默认情况下 Spring returns 一个 500。
对于 Web 服务,这通常 不是 内部服务器错误,不应该是 500,而是 400,这是一个错误的请求。
只要这样做有助于捕获编程错误,即错误,请大量使用断言。
不要使用断言来捕获逻辑上可能发生的事情,即格式错误的输入。仅当错误不可恢复时才使用断言。
不要在检查断言时运行的代码中放置任何生产逻辑。如果你的软件写得很好,这是微不足道的事实,但如果不是,那么你可能会产生微妙的副作用,以及启用和禁用断言时不同的整体行为。
如果您的公司有 "testing code" 和 "production code" 做同样的事情但作为不同的代码库(或不同的编辑阶段),离开那里,永远不要回来。试图解决这种水平的无能可能是在浪费你的时间。 如果您的公司没有在测试代码之外放置任何断言语句,请告诉他们断言在生产构建中被禁用,如果没有,那么修复该错误现在是您的首要任务。
断言的价值恰恰是要在业务逻辑内部使用,而不仅仅是测试套件。 这使得产生许多高级测试变得容易,这些测试不必显式测试很多东西来遍历大块代码并触发所有这些断言。 在我的一些项目中,典型的测试甚至没有真正断言任何东西,它们只是命令根据特定输入进行计算,这导致需要检查数百个断言,甚至在微小的逻辑片段中也会发现问题。