Java断言关键字的作用是什么,以及何时应该使用它?

为了理解断言的关键作用,一些真实的例子是什么?


在Java 1.4中添加了断言(通过assert关键字)。 它们用于验证代码中不变量的正确性。 它们不应该在生产代码中被触发,并且指示代码路径的错误或误用。 它们可以通过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;
}

我们假设你应该编写一个程序来控制核电站。 很明显,即使是最小的错误也会导致灾难性的结果,因此你的代码必须是无 bug的(假设JVM因为参数而没有错误)。

Java不是一种可验证的语言,这意味着:您无法计算出您的操作结果是完美的。 其主要原因是指针:它们可以指向任何地方或任何地方,因此无法计算出它的精确值,至少不在合理的代码范围内。 鉴于此问题,无法证明您的代码在整体上是正确的。 但是你可以做的是证明你至少在发生错误时找到每一个bug。

这个想法基于契约设计(DbC)范式:您首先定义(以数学精度)您的方法应该执行的操作,然后在实际执行过程中对其进行验证。 例:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

虽然这很明显工作正常,但大多数程序员不会看到这个内部的隐藏的bug(提示:由于类似的bug,Ariane V崩溃)。 现在,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;
}

如果这个功能现在失败了,你会注意到它。 你会知道你的代码有问题,你知道它在哪里,你知道是什么造成了它(类似于Exceptions)。 而更重要的是:当它发生时阻止任何进一步的代码使用错误的值工作,并可能对其控制的任何操作造成损害。

Java异常是一个类似的概念,但它们未能验证所有内容。 如果你想要更多的检查(以执行速度为代价),你需要使用断言。 这样做会使你的代码膨胀,但最终你可以在一个非常短的开发时间内交付产品(越早修复bug,成本越低)。 此外:如果您的代码中存在任何错误,您将检测到它。 没有任何方法可能导致错误发生并在稍后导致问题。

这仍然不是无错代码的保证,但它比通常的程序更接近于此。


断言是一种开发阶段工具,用于捕获代码中的错误。 它们被设计为易于移除,因此它们不会存在于生产代码中。 因此,断言不是您提供给客户的“解决方案”的一部分。 他们是内部检查,以确保你所做的假设是正确的。 最常见的例子是测试null。 许多方法都是这样写的:

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
}

这样,你一定会尽早发现这个错误。 (在合约中指定此参数不应为空值也很有用)。在开发过程中测试代码时,一定要打开断言。 (说服你的同事也这样做通常很困难,我觉得很烦人。)

现在,您的一些同事会反对此代码,认为您应该仍然使用空检查来防止生产中发生异常。 在这种情况下,断言仍然有用。 你可以这样写:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

通过这种方式,您的同事会很高兴在生产代码中使用空检查,但在开发过程中,当小部件为空时,您不再隐藏缺陷。

下面是一个真实世界的例子:我曾经写过一个方法,比较两个任意值是否相等,其中任何一个值都可以为null:

/**
 * 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不为null的情况下委托equals()方法的工作。 但它假定equals()方法通过正确处理null参数正确地实现了equals()的约定。

一位同事反对我的代码,告诉我很多我们的类有bug的equals()方法,它们不测试null,所以我应该把这个检查放到这个方法中。 如果这是明智的,或者如果我们应该强制这个错误,这是值得商榷的,所以我们可以发现它并修复它,但是我推迟到我的同事那里,并且输入了一个空的支票,我已经标记了一条评论:

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;
}

如果equals()方法没有根据合约要求检查other != null ,则只需要other != null

我并没有和我的同事进行一场毫无结果的辩论,让我们的代码保留在代码库中,我只是在代码中放置了两个断言。 这些断言会让我知道,在开发阶段,如果我们的某个类无法正确实现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;
}

需要牢记的重点是:

  • 断言仅是开发阶段工具。

  • 断言的要点是让你知道是否有一个错误,不仅在你的代码中,而且在你的代码库中。 (这里的断言实际上会标记其他类中的错误。)

  • 即使我的同事确信我们的课程写得很好,这里的主张仍然有用。 将添加新的类,可能无法测试为null,并且此方法可以为我们标记这些错误。

  • 在开发中,即使您编写的代码不使用断言,您也应该始终开启断言。 我的IDE设置为默认为任何新的可执行文件执行此操作。

  • 这些断言不会改变生产中代码的行为,所以我的同事很高兴有空检查,并且即使equals()方法有问题,该方法也会正确执行。 我很高兴,因为我将在开发中捕获任何bug的equals()方法。

  • 另外,您应该通过放入一个临时断言来测试断言策略,这样可以确保您可以通过日志文件或输出流中的堆栈跟踪来通知您。

    链接地址: http://www.djcxy.com/p/50225.html

    上一篇: What does the Java assert keyword do, and when should it be used?

    下一篇: Learning/Implementing Design Patterns (For Newbies)