有哪些真实的例子来理解断言的关键作用?
当前回答
什么时候应该使用Assert ?
很多很好的答案解释了assert关键字的作用,但很少回答真正的问题,“在现实生活中什么时候应该使用assert关键字?”答案是:
几乎从来没有
断言,作为一个概念,是很棒的。好的代码有很多if(…)throw…语句(以及它们的亲戚,如对象。requireNonNull和Math.addExact)。然而,某些设计决策极大地限制了assert关键字本身的效用。
assert关键字背后的驱动思想是不成熟的优化,其主要特性是能够轻松地关闭所有检查。事实上,断言检查在默认情况下是关闭的。
然而,在生产中继续执行不变检查是非常重要的。这是因为完美的测试覆盖率是不可能的,所有的产品代码都会有错误,而断言应该有助于诊断和减轻这些错误。
因此,if(…)的使用抛出…应该是首选的,就像检查公共方法的参数值和抛出IllegalArgumentException时需要它一样。
偶尔,人们可能会被诱惑编写一个不变检查,它确实需要很长时间来处理(并且经常被调用,以至于它很重要)。然而,这样的检查将减缓测试,这也是不可取的。这种耗时的检查通常被写成单元测试。然而,出于这个原因,有时使用assert是有意义的。
不要使用assert,因为它比if(…)throw…(我非常痛苦地说,因为我喜欢干净和漂亮)。如果您无法控制自己,并且可以控制应用程序的启动方式,那么可以随意使用断言,但始终在生产环境中启用断言。不可否认,这是我倾向于做的事情。我正在推动一个lombok注释,它将导致assert的行为更像if(…)throw ....请在这里投票。
(咆哮:JVM开发者是一群糟糕的、过早优化的编码员。这就是为什么你会在Java插件和JVM中听到这么多安全问题。他们拒绝在产品代码中包含基本的检查和断言,而我们正在继续为此付出代价。)
其他回答
让我们假设您要编写一个控制核电站的程序。很明显,即使是最微小的错误也可能导致灾难性的结果,因此您的代码必须是无bug的(为了论证,假设JVM是无bug的)。
Java不是一种可验证的语言,这意味着:你不能计算出你的操作结果会是完美的。这样做的主要原因是指针:它们可以指向任何地方,也可以指向任何地方,因此它们不能被计算为这个确切的值,至少在合理的代码范围内不能。对于这个问题,没有办法证明您的代码在整体上是正确的。但你能做的是证明你至少能在bug发生时找到它。
此思想基于契约式设计(Design-by-Contract, DbC)范式:首先定义(具有数学精度)您的方法应该做什么,然后在实际执行期间通过测试来验证这一点。例子:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
return a + b;
}
虽然这很明显可以正常工作,但大多数程序员不会看到其中隐藏的bug(提示:Ariane V因为类似的bug而崩溃)。现在DbC定义您必须始终检查函数的输入和输出,以验证它是否正确工作。Java可以通过断言来做到这一点:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
final int result = a + b;
assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
return result;
}
如果这个函数现在失败了,您会注意到它。你会知道你的代码中有问题,你知道它在哪里,你知道是什么引起的(类似于异常)。更重要的是:当它发生时停止正确执行,以防止任何进一步的代码使用错误的值,并可能对它所控制的任何东西造成损害。
Java异常是一个类似的概念,但它们不能验证所有内容。如果需要更多的检查(以降低执行速度为代价),则需要使用断言。这样做会使代码膨胀,但最终可以在短得惊人的开发时间内交付产品(越早修复bug,成本就越低)。此外,如果代码中有任何错误,您将检测到它。不可能出现漏洞并在以后引起问题。
这仍然不能保证代码没有错误,但它比通常的程序更接近于这一点。
断言(通过assert关键字)是在Java 1.4中添加的。它们用于验证代码中不变量的正确性。它们永远不应该在生产代码中触发,并且指示了代码路径的错误或误用。它们可以在运行时通过java命令上的-ea选项激活,但默认情况下不会打开。
一个例子:
public Foo acquireFoo(int id) {
Foo result = null;
if (id > 50) {
result = fooService.read(id);
} else {
result = new Foo(id);
}
assert result != null;
return result;
}
基本上,“断言为真”会通过,“断言为假”会失败。让我们看看这是如何工作的:
public static void main(String[] args)
{
String s1 = "Hello";
assert checkInteger(s1);
}
private static boolean checkInteger(String s)
{
try {
Integer.parseInt(s);
return true;
}
catch(Exception e)
{
return false;
}
}
我不知道Java中这个特性背后的历史。但是我有一个关于断言可以用来做什么的想法,我认为在这个帖子中没有提到过。
假设你有一个枚举,你打开它:(†)
public enum Veggie {
CAULIFLOWER,
CARROT,
}
// Another class
switch (veggie) {
case CAULIFLOWER: value = 5;
case CARROT: value = 3;
}
†:也许优秀的Java编译器/工具会静态地捕获丢失的枚举值。在这种情况下,想象一下你发现了一些bug并修复了它。然后,您可以使用下面描述的技术在代码中适当地添加回归测试。
这里没有好的默认情况。所以你要确保你覆盖了所有的值。当你添加一个新的enum变量时,你要确保你更新了switch块:
public enum Veggie {
CAULIFLOWER,
CARROT,
TOMATO,
}
但事实证明,当用户在web应用程序中加载特定视图时,这段代码会被调用。尽管存在这个错误,视图仍然可以持续存在:这将是一个bug,但不值得干扰视图的加载(让我们说用户只是看到了一些错误的数字)。所以你只需要记录一个警告:
switch (veggie) {
case CAULIFLOWER: value = 5;
case CARROT: value = 3;
default: nonExhaustiveMatchOnEnum();
// […]
public static void nonExhaustiveMatchOnEnum() {
String errorMessage: "Missing veggie";
logger.warn(errorMessage);
}
但是当你在开发代码时,你确实希望这个错误立即失败——开发者可以在五分钟内修复这个错误,而不像你的web应用程序的用户。
所以你可以添加一个assert false:
public static void nonExhaustiveMatchOnEnum() {
String errorMessage: "Missing veggie";
assert false : errorMessage;
logger.warn(errorMessage);
}
现在,应用程序将在本地崩溃,但只在生产中记录一个警告(假设您在本地使用java -ea,而不是在生产中)。
除了这里提供的所有很棒的答案之外,官方Java SE 7编程指南还提供了关于使用assert的非常简洁的手册;通过几个准确的例子,说明了何时使用断言是一个好主意(重要的是,也是坏主意),以及它与抛出异常有何不同。
Link
推荐文章
- 等待未来的名单
- 如何检查JSON键是否存在?
- 为什么MongoDB Java驱动在条件中使用随机数生成器?
- 即使从未抛出异常,使用try-catch块的代价是否昂贵?
- 什么时候我们应该使用观察者和可观察对象?
- Java中的split()方法对点(.)不起作用。
- Eclipse调试器总是阻塞在ThreadPoolExecutor上,没有任何明显的异常,为什么?
- Java生成两个给定值之间的随机数
- 如何有效地从数组列表或字符串数组中删除所有空元素?
- 比较JUnit断言中的数组,简洁的内置方式?
- codestyle;把javadoc放在注释之前还是之后?
- 如何在Spring中定义List bean ?
- 将Set<T>转换为List<T>的最简洁的方法
- 在JavaScript中,什么相当于Java的Thread.sleep() ?
- 使用Java重命名文件