断言与传统代码的比较
• If-then-else 风格 : if (i % 3 != 0) { if (i < 0) { System.err.println("Error in Variable i"); return -1; } System.out.println("Change $"+i%3); } • 断言风格 : if (i % 3 != 0) { assert i > 0; System.out.println("Change $"+i%3); } 以上两段代码的工作方式几乎完全相同。如果变量 i 小于 0 (这是不正常的),第一段代码会报 告 Error in Variable i 。第二段代码则引发一个断言错误,指出发生错误的行号。显然,断言方 式所需的行数要少得多。 • 有三种方式可在 Java 代码中使用断言。下表对此进行 了总结。 语法 例子 assert Expression1; assert i%3==2; assert Expression1 : Expression2; assert i%3==2 : "Wow, Error"; throw new AssertionError(Expression1); throw new AssertionError("Oh no"); 使用断言时的编译和运行命令 • 在代码中插入断言语句后,需要编译和运行代码,但具体采用的方式有别于 从前。使用断言时,要用以下命令行语句进行编译: – javac -source 1.4 MyCode.java -source 选项强迫编译的代码提供与指定版本( 1.4 )的源码兼容性。这是必需 的,因为断言是从 JDK 1.4 开始新增的功能。程序编译成与版本 1.4 兼容后,要用 以下命令来运行程序: – java -ea MyCode - ea 选项要求系统在程序执行期间启用断言特性。如果省略该选项,程序就不会 显示断言异常消息。 如果只想在一个指定的包中启用断言,比如在 com.lawrence.teki.chan 包中,你 可在 -ea 选项之后包括这个包: java -ea:com.lawrence.teki.chan MyCode 记住,断言并不是用来替代 if-then-else 或者其他语法的。从本质上说,断言只 是调试程序和修订算法的一种高效工具。 assertion 与继承 • 如果开启一个子类的 assertion ,那么它的父类的 assertion 是否执行? – 下面的例子将显示如果一个 assert 语句在父类,而当它的子类调用它时,该 assert 为 false 。我们看看在不同的情况下,该 assertion 是否被处理。 class Base{ public void baseMethod(){ assert false : “Assertion failed:This is base ”; // 总是 assertion 失败 System.out.println(“Base Method”); } } class Derived extends Base{ public void derivedMethod(){ assert false : “Assertion failed:This is derive”; // 总是 assertion 失败 System.out.println( "Derived Method" ); } public static void main( String[] args ){ try { Derived derived = new Derived(); derived.baseMethod( ); derived.derivedMethod(); } catch ( AssertionError ae ){ System.out.println(ae); } } } 运行命令 含义 结果 Java Derived 不启用 assertion Base Method Derived Method Java -ea Derived 开启所有 assertion Java.lang.AssertionError:Ass ertion Failed:This is base Java -da Derived 关闭所有 assertion Base Method Derived Method Java -ea:Base Derived 仅打开 Base 的 assertion Java.lang.AssertionError:Ass ertion Failed:This is base Java -ea:Derived Derived 仅打开 Derived 的 assertion Base Method Java.lang.AssertionError:Ass ertion Failed:This is derived 从这个例子我们可以看出,父类的 assert 语句将只有在父类的 assert 开启才起作用, 如果仅仅开启子类的 assert ,父类的 assert 仍然不运行。例如,我们执行 java - ea:Derived Derived 的时候, Base 类的 assert 语句并不执行。因此,我们可以认为, assert 语句不具有继承功能。 断言使用准则 • assertion 的使用是一个复杂的问题 – 这将涉及到程序的风格, assertion 运用的目标,程序的性质等问题 – 断言应被视为对程序进行验证的一种工具,而不是把它当作程序的一部分 – assertion 表达式应该短小、易懂,如果需要评估复杂的表达式,应该使用函数计算 • assertion 适用的地方 – assertion 用于检查一些关键的值,并且这些值对整个程序,或者局部功能的完成有很大的 影响,并且这种错误不容易恢复的 – assertion 用于验证逻辑。断言是具有重要价值的一种调试工具。它提供了一种清晰和可跟 踪的形式来验证程序逻辑。它还提供了一种灵活和独立的策略来帮助你进行开发,而且最 终可以从程序的生产版本中拿掉 • assertion 不适用的地方 – 不适合在公共方法中使用断言参数检查,参数检查通常是一个合约的一部分,无论断言是 否启用都应该执行。错误发生时,应该引发恰当的运行时异常,比如 IndexOutOfBoundsException 或者 NullPointerException ,而不是引发常规的断言错误。 – 不建议为一个必需的操作使用断言,由于断言可以启用或禁用,所以不应将操作与断言混 合。断言只是一种调试工具 • 检查控制流 : – 在 if-then-else 和 swith-case 语句中,我们可以在不应该发生的控制支流上加上 assert false 语句。如果这种情况发生了, assert 能够检查出来 例如: x 取值只能是 1,2,3 ,我们的程序可以如下表示: switch (x){ case 1: …; case 2: … ; case 3: … default: assert false:"x value is invalid: "+x; } • 在私有函数计算前,检查输入参数是否有效: – 对于公共函数,我们通常不使用 assertion 检查,因为一般来说,公共函数必须对无效的参 数进行检查和处理。而私有函数往往是直接使用的。 例如:某函数可能要求输入的参数必须不为 null 。那么我们可以在函数的一开始加上: assert parameter1!=null : “paramerter is null in test method”; • 在函数计算后,检查函数结果是否有效: – 对于一些计算函数,函数运行完成后,某些值需要保证一定的值域。 例如:我们有一个计算绝对值的函数,那么我们就可以在函数的结果处,加上一个语句: assert value>=0:“Value should be bigger than 0:”+value; 通过这种方式,我们可以对函数计算完的结果进行检查。 • 检查程序不变量 – 有些程序中,存在一些不变量,在程序的运行生命周期,这些不变量的值都是不变的。这 些不变量可能是一个简单表达式,也可能是一个复杂的表达式。对于一些关键的不变量, 我们可以通过 assert 进行检查。 例如,在一个财会系统中,公司的支出和收入必须保持一定的平衡关系,因此我们可以编 写一个表达式检查这种平衡关系,如下表示: private boolean isBalance() { …… } 在这个系统中,在一些可能影响这种平衡关系的方法的前后,我们都可以加上 assert 验 证: assert isBalance():"balance is destoried"; 不使用 assertion 的情况 • assert names.remove(null); names.remove(null) 本来是程序中的一个有效的语句,在你的算法中应该总是返回 true 。用断言来检查它虽然可行,但一旦断言被禁用,该语句就会被跳过。所以,无 论如何都应该将断言和正确的操作分开,例如: • boolean nullsRemoved = names.remove(null); assert nullsRemoved; 在断言被启用的前提下,第二个语句检查返回值,第一个语句则总是执行 names.remove(null) 操作。这样可确保断言不会影响正常操作。 虽然可能要将断言从最终产品中剥离,但没有工具能自动删除不再需要的断言。不 过,采取以下方式,也许能帮助你在编译时跳过断言语句: • static final boolean isAssertUsed = true; if (isAssertUsed) assert MyCondition; 上述代码取代了单行语句 assert MyCondition 。如果 isAssertUsed 为 true ,它会触发 断言。如果编译时不需要断言,将 isAssertUsed 的值变成 false 就可以了:
• static final boolean isAssertUsed = false; if (isAssertUsed) assert MyCondition; 由于条件 isAssertUsed 总是为 false ,所以 if 内部的内容是一个不可读的区域,编译器 会自动跳过这个区域。这称为 “ 条件编译 ” 。进行上述修改并重新编译了代码后,程序 的生产版本将不再含有断言。 • 还建议你在初始化一个类之前,检查断言是否启用。在断言被禁用的情况下,以下 代码有助于防止一个类被初始化: • static { boolean isAssertEnabled = false; assert isAssertEnabled = true; if (!isAssertEnabled) throw new RuntimeException("Assertion must be enabled!"); } 断言启用的前提下, assert isAssertEnabled = true 语句可将变量 isAssertEnabled 设 为 true 。在断言被禁用的前提下,会跳过该语句(变量依然为 false )。所以,如果断 言是禁用的,就会运行 if 语句并引发异常。
