有哪些真实的例子来理解断言的关键作用?


当前回答

总结一下(不仅仅是Java,很多语言都是如此):

“assert”主要被软件开发人员在调试过程中用作调试辅助。断言消息永远不会出现。许多语言提供了一个编译时选项,该选项将导致所有“断言”被忽略,用于生成“生产”代码。

"exceptions" are a handy way to handle all kinds of error conditions, whether or not they represent logic errors, because, if you run into an error-condition such that you cannot continue, you can simply "throw them up into the air," from wherever you are, expecting someone else out there to be ready to "catch" them. Control is transferred in one step, straight from the code that threw the exception, straight to the catcher's mitt. (And the catcher can see the complete backtrace of calls that had taken place.)

此外,该子例程的调用者不必检查子例程是否成功:“如果我们现在在这里,它一定成功了,因为否则它会抛出异常,我们现在就不会在这里!”这种简单的策略使得代码设计和调试变得非常非常容易。

异常方便地允许致命错误条件保持原样:“规则的异常”。而且,对于它们来说,由代码路径来处理,这也是“规则的例外……“高飞球!”

其他回答

断言是可以被关闭的检查。它们很少被使用。为什么?

它们不能用于检查公共方法参数,因为您无法控制它们。 它们不应该用于简单的检查,如result != null,因为这样的检查非常快,几乎没有什么可以保存。

那么,还剩下什么呢?昂贵的条件检查是真实的。像rb树这样的数据结构的不变量就是一个很好的例子。实际上,在JDK8的ConcurrentHashMap中,对于TreeNodes有一些这样有意义的断言。

您确实不希望在生产环境中启用它们,因为它们很容易控制运行时间。 您可能希望在测试期间打开或关闭它们。 在处理代码时一定要打开它们。


有时候,支票不是很贵,但与此同时,你很确定,它会通过。在我的代码中,有例如,

assert Sets.newHashSet(userIds).size() == userIds.size();

我很确定我刚刚创建的列表有独特的元素,但我想记录并再次检查它。

断言(通过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;
}

断言允许检测代码中的缺陷。您可以打开断言进行测试和调试,而在程序处于生产状态时关闭断言。

既然你知道它是真的,为什么还要坚持呢?只有当一切都正常工作时,这才是正确的。如果程序有一个缺陷,它实际上可能不是真的。在过程早期检测到这一点可以让您知道哪里出了问题。

assert语句包含此语句以及可选的String消息。

assert语句的语法有两种形式:

assert boolean_expression;
assert boolean_expression: error_message;

下面是一些基本规则,它们控制着断言应该在哪里使用,不应该在哪里使用。断言应该用于:

验证私有方法的输入参数。不是公共方法。当传递坏参数时,公共方法应该抛出常规异常。 在程序的任何地方来确保一个事实的有效性,而这个事实几乎肯定是正确的。

例如,如果你确定它只会是1或2,你可以使用这样的断言:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...

在任何方法结束时验证后置条件。这意味着,在执行业务逻辑之后,您可以使用断言来确保变量或结果的内部状态与您所期望的一致。例如,打开套接字或文件的方法可以在末尾使用断言来确保确实打开了套接字或文件。

断言不应该用于:

验证公共方法的输入参数。由于断言可能并不总是被执行,因此应该使用常规异常机制。 对用户输入的内容验证约束。同上。 不应用于副作用。

例如,这不是一个正确的用法,因为断言在这里被用于调用doSomething()方法的副作用。

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

唯一可以证明这一点的情况是,当你试图找出断言是否在你的代码中启用时:

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}

下面是我在Hibernate/SQL项目的服务器中编写的断言。一个实体bean有两个有效的布尔属性,称为isActive和isDefault。每个都可以有一个值“Y”或“N”或null,被视为“N”。我们希望确保浏览器客户端仅限于这三个值。所以,在这两个属性的setter中,我添加了这样的断言:

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

注意以下几点。

This assertion is for the development phase only. If the client sends a bad value, we will catch that early and fix it, long before we reach production. Assertions are for defects that you can catch early. This assertion is slow and inefficient. That's okay. Assertions are free to be slow. We don't care because they're development-only tools. This won't slow down the production code because assertions will be disabled. (There's some disagreement on this point, which I'll get to later.) This leads to my next point. This assertion has no side effects. I could have tested my value against an unmodifiable static final Set, but that set would have stayed around in production, where it would never get used. This assertion exists to verify the proper operation of the client. So by the time we reach production, we will be sure that the client is operating properly, so we can safely turn the assertion off. Some people ask this: If the assertion isn't needed in production, why not just take them out when you're done? Because you'll still need them when you start working on the next version.

Some people have argued that you should never use assertions, because you can never be sure that all the bugs are gone, so you need to keep them around even in production. And so there's no point in using the assert statement, since the only advantage to asserts is that you can turn them off. Hence, according to this thinking, you should (almost) never use asserts. I disagree. It's certainly true that if a test belongs in production, you should not use an assert. But this test does not belong in production. This one is for catching a bug that's not likely to ever reach production, so it may safely be turned off when you're done.

顺便说一句,我可以这样写:

assert value == null || value.equals("Y") || value.equals("N") : value;

对于只有三个值是可以的,但是如果可能值的数量变大,HashSet版本就会变得更方便。我选择HashSet版本来说明我关于效率的观点。

什么时候应该使用Assert ?

很多很好的答案解释了assert关键字的作用,但很少回答真正的问题,“在现实生活中什么时候应该使用assert关键字?”答案是:

几乎从来没有

断言,作为一个概念,是很棒的。好的代码有很多if(…)throw…语句(以及它们的亲戚,如对象。requireNonNull和Math.addExact)。然而,某些设计决策极大地限制了assert关键字本身的效用。

assert关键字背后的驱动思想是不成熟的优化,其主要特性是能够轻松地关闭所有检查。事实上,断言检查在默认情况下是关闭的。

然而,在生产中继续执行不变检查是非常重要的。这是因为完美的测试覆盖率是不可能的,所有的产品代码都会有错误,而断言应该有助于诊断和减轻这些错误。

因此,if(…)的使用抛出…应该是首选的,就像检查公共方法的参数值和抛出IllegalArgumentException时需要它一样。

偶尔,人们可能会被诱惑编写一个不变检查,它确实需要很长时间来处理(并且经常被调用,以至于它很重要)。然而,这样的检查将减缓测试,这也是不可取的。这种耗时的检查通常被写成单元测试。然而,出于这个原因,有时使用assert是有意义的。

不要使用assert,因为它比if(…)throw…(我非常痛苦地说,因为我喜欢干净和漂亮)。如果您无法控制自己,并且可以控制应用程序的启动方式,那么可以随意使用断言,但始终在生产环境中启用断言。不可否认,这是我倾向于做的事情。我正在推动一个lombok注释,它将导致assert的行为更像if(…)throw ....请在这里投票。

(咆哮:JVM开发者是一群糟糕的、过早优化的编码员。这就是为什么你会在Java插件和JVM中听到这么多安全问题。他们拒绝在产品代码中包含基本的检查和断言,而我们正在继续为此付出代价。)