1.6 要决断:使用Java断言
Java5+
“编程人员总是正确的—— 是编译器和解释器造成的错误。”我确信你认同这种说法。作为编程人员,经常要对变量的值做出假设并且基于此编写代码。尽管非常不愿意承认可能在设计或实现上有错误,但有时变量和参数却没有获得期望的值。
当设计和编写代码时,只有在最初的假设仍然成立的情况下代码才能正确运行。如果没有任何有关这些假设的声明,那么阅读代码的任何人(甚至你自己)都不清楚它们的含义是什么。从而导致今后的改动可能会违反这些假设并引入难以查找的错误。通常,在注释中说明假设能使以后修改代码的人避免出错。
使用注释来说明假设是一个好的开始。但是当出现违反假设的情况时,程序有时会继续运行就像未出现任何问题一样。一些情况下,开发人员能够马上看到结果,并且可以纠正出现的问题。但在另一些情况下,存在一个潜伏的错误时,可能会对应用程序的其他部分造成负面影响,对于分布式系统而言,则可能会对完全不同的另一个应用程序造成负面影响!跟踪这样的问题非常困难。
Java 1.4在语言中添加了断言特性来简化测试和调试,加强文档编制并提高基于Java的可维护性。可以使用一个布尔表达式来创建一个断言,以便测试有关系统的当前状态所假定的某些情况。如果断言失败,运行库会抛出一个AssertionError。下面给出一个很简单的断言:
String name = "Brian";
assert name != null;
|
这里,可以确定在给name分配了值“Brian”后它的值将不会为空。如果它的值为空,则出现了某种严重的错误!此断言是当时在程序中对变量的值所做的假定的声明。为如此简单的例子做这种声明看似可笑和多余。但是,当多个方法会影响一个对象的状态时,这种方法是有效的。在下面的示例中,示例了这样一个断言,即在向新员工分配任何任务之前必须已经指派了一名管理人员。
Employee worker =
new Employee("John", "Smith", 100000, "Developer");
assignOffice(worker);
setUpVoiceMail(worker);
moreAdministrivia(worker);
assert worker.getSupervisor() != null : "Supervisor cannot be null";
assignTasks(worker);
|
通常,在对某个对象执行关键操作时会需要对它创建断言。这有助于增强代码的健壮性,比如如果在程序中出现了某种错误,可以更方便地调试程序。这样做要比程序在某处执行失败造成不良后果来发现错误要好得多。当知道程序失败是由于它违反了假设而引起的时候,跟踪失败的原因要简单得多。在这个代码样例中,使用了assert选项来返回更有用的信息。没有此选项时,除了行号之外将无法得到有关断言的标识信息。
在某些版本的编译器上,当编译源代码时需要使用一个命令选项来设置编译器的源兼容性模式(取决于编译器的版本,如1.4或1.5)。
javac -source 1.5 MyClass.java
|
现在强制断言失败并观察会出现什么情况:
public class AssertBad {
public static void main(String[] args) {
int total = 20;
int itemCount = 0;
assert itemCount > 0;
int average = total / itemCount;
}
}
|
默认情况下,运行时环境不支持断言,必须使用ea(允许断言)命令选项来启动JRE。上面的代码会引起以下的结果:
C:\projects\wcj1> java -ea AssertBad
Exception in thread "main" java.lang.AssertionError
at AssertBad.main(AssertBad.java:12)
|
要记住断言是用来对那些不应该出现的情况进行实际的“健全性检查”,因此不应该使用它们来替代常规的错误检查。
警告:
不要让断言语句更改代码中的状态/值。否则当最终关闭断言时,代码的行为方式将不同于启用断言时代码的行为。例如,不要创建如下的断言:
assert (++i > 10); // BAD: i changes only with assertions enabled!
|
通常,在整个开发阶段都会启用断言。一旦完全测试了系统并将它移送到产品环境时,则希望禁用断言,因为这样做会略微改善性能。但是不要改动代码来完成此操作,并且也不要删除断言。不管怎样,为了编制文档的目的,断言也应保留在代码中。这样,当以后更改代码时,会提醒程序员要保持所有假设都是有效的,并且这也是可测试的。