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


当前回答

断言主要用于调试应用程序,或者用于替换某些应用程序的异常处理,以检查应用程序的有效性。

断言在运行时工作。这里有一个简单的例子,可以非常简单地解释整个概念——assert关键字在Java中做什么?(WikiAnswers)。

其他回答

总结一下(不仅仅是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.)

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

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

断言主要用于调试应用程序,或者用于替换某些应用程序的异常处理,以检查应用程序的有效性。

断言在运行时工作。这里有一个简单的例子,可以非常简单地解释整个概念——assert关键字在Java中做什么?(WikiAnswers)。

我不知道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,而不是在生产中)。

一个真实的例子,来自一个stack类(来自Java文章中的断言)

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

这是另一个例子。我写了一个方法来查找两个排序数组中值的中位数。该方法假设数组已经排序。出于性能考虑,它不应该首先对数组排序,甚至不应该检查以确保它们已排序。然而,对未排序的数据调用此方法是一个严重的错误,我们希望在开发阶段尽早发现这些错误。下面是我处理这些看似矛盾的目标的方法:

public static int medianOf(int[] a, int[] b) {
  assert assertionOnlyIsSorted(a);      // Assertion is order n
  assert assertionOnlyIsSorted(b);
  ... // rest of implementation goes here. Algorithm is order log(n)
}

public static boolean assertionOnlyIsSorted(int[] array) {
  for (int i=1; i<array.length; ++i) {
    if (array[i] < array[i-1]) {
      return false;
    }
    return true;
  }
}

这样,缓慢的测试只在开发阶段执行,在开发阶段,速度没有捕获错误重要。您希望medianOf()方法具有log(n)性能,但“is sorted”测试是o (n)。因此,我将其放在断言中,以限制其在开发阶段的使用,并为其命名,以明确表示它不适合生产。

这样我就两全其美了。在开发过程中,我知道任何不正确地调用这个函数的方法都会被捕获并修复。而且我知道这样做的缓慢测试不会影响生产中的性能。(这也很好地说明了为什么要在生产环境中关闭断言,而在开发环境中启用断言。)