有哪些真实的例子来理解断言的关键作用?
当前回答
我不知道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,而不是在生产中)。
其他回答
断言是用于捕获代码中的错误的开发阶段工具。它们被设计为易于删除,因此它们不会存在于生产代码中。因此断言不是您交付给客户的“解决方案”的一部分。它们是内部检查,以确保你所做的假设是正确的。最常见的例子是测试是否为空。很多方法都是这样写的:
void doSomething(Widget widget) {
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
在这样的方法中,小部件通常不应该是空的。所以如果它是空的,在你的代码中有一个bug,你需要追踪。但是上面的代码永远不会告诉你这一点。因此,在编写“安全”代码的善意努力中,您也隐藏了一个错误。这样写代码会更好:
/**
* @param Widget widget Should never be null
*/
void doSomething(Widget widget) {
assert widget != null;
widget.someMethod(); // ...
... // do more stuff with this widget
}
这样,您一定能尽早发现这个错误。(在合同中指定这个参数永远不应该为空也是有用的。)在开发过程中测试代码时,一定要打开断言。(说服你的同事这样做通常也很困难,我觉得这很烦人。)
现在,您的一些同事会反对这段代码,认为您仍然应该放入null检查,以防止生产中出现异常。在这种情况下,断言仍然有用。你可以这样写:
void doSomething(Widget widget) {
assert widget != null;
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
这样,您的同事就会高兴地看到产品代码有空检查,但在开发过程中,当小部件为空时,您就不再隐藏错误了。
这里有一个真实的例子:我曾经写过一个方法,比较两个任意值是否相等,其中任何一个值都可以为空:
/**
* Compare two values using equals(), after checking for null.
* @param thisValue (may be null)
* @param otherValue (may be null)
* @return True if they are both null or if equals() returns true
*/
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = thisValue.equals(otherValue);
}
return result;
}
这段代码在thisValue不为空的情况下委托equals()方法的工作。但它假设equals()方法通过正确处理空参数正确地实现了equals()的契约。
一位同事反对我的代码,告诉我我们的许多类都有不测试null的equals()方法,所以我应该把这个检查放到这个方法中。这是否是明智的,或者我们是否应该强制错误,这样我们就可以发现并修复它,这是有争议的,但我听从了我同事的意见,放入了一个空检查,我已经标记了一个注释:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = otherValue != null && thisValue.equals(otherValue); // questionable null check
}
return result;
}
这里的额外检查other != null仅在equals()方法不能按照其契约要求检查null时才有必要。
我没有与同事就让有bug的代码留在代码库中是否明智进行毫无结果的争论,而是简单地在代码中放入了两个断言。这些断言会让我知道,在开发阶段,如果我们的一个类不能正确地实现equals(),所以我可以修复它:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
assert otherValue == null || otherValue.equals(null) == false;
} else {
result = otherValue != null && thisValue.equals(otherValue);
assert thisValue.equals(null) == false;
}
return result;
}
需要记住的要点如下:
Assertions are development-phase tools only. The point of an assertion is to let you know if there's a bug, not just in your code, but in your code base. (The assertions here will actually flag bugs in other classes.) Even if my colleague was confident that our classes were properly written, the assertions here would still be useful. New classes will be added that might fail to test for null, and this method can flag those bugs for us. In development, you should always turn assertions on, even if the code you've written doesn't use assertions. My IDE is set to always do this by default for any new executable. The assertions don't change the behavior of the code in production, so my colleague is happy that the null check is there, and that this method will execute properly even if the equals() method is buggy. I'm happy because I will catch any buggy equals() method in development.
此外,您应该通过放入一个将失败的临时断言来测试断言策略,这样您就可以确定通过日志文件或输出流中的堆栈跟踪来通知您。
除了这里提供的所有很棒的答案之外,官方Java SE 7编程指南还提供了关于使用assert的非常简洁的手册;通过几个准确的例子,说明了何时使用断言是一个好主意(重要的是,也是坏主意),以及它与抛出异常有何不同。
Link
断言允许检测代码中的缺陷。您可以打开断言进行测试和调试,而在程序处于生产状态时关闭断言。
既然你知道它是真的,为什么还要坚持呢?只有当一切都正常工作时,这才是正确的。如果程序有一个缺陷,它实际上可能不是真的。在过程早期检测到这一点可以让您知道哪里出了问题。
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");
}
断言是可以被关闭的检查。它们很少被使用。为什么?
它们不能用于检查公共方法参数,因为您无法控制它们。 它们不应该用于简单的检查,如result != null,因为这样的检查非常快,几乎没有什么可以保存。
那么,还剩下什么呢?昂贵的条件检查是真实的。像rb树这样的数据结构的不变量就是一个很好的例子。实际上,在JDK8的ConcurrentHashMap中,对于TreeNodes有一些这样有意义的断言。
您确实不希望在生产环境中启用它们,因为它们很容易控制运行时间。 您可能希望在测试期间打开或关闭它们。 在处理代码时一定要打开它们。
有时候,支票不是很贵,但与此同时,你很确定,它会通过。在我的代码中,有例如,
assert Sets.newHashSet(userIds).size() == userIds.size();
我很确定我刚刚创建的列表有独特的元素,但我想记录并再次检查它。
断言主要用于调试应用程序,或者用于替换某些应用程序的异常处理,以检查应用程序的有效性。
断言在运行时工作。这里有一个简单的例子,可以非常简单地解释整个概念——assert关键字在Java中做什么?(WikiAnswers)。
推荐文章
- 等待未来的名单
- 如何检查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重命名文件