第一節(jié)          斷言 assert

§1.1.1                     assertion的語(yǔ)法和語(yǔ)義

J2SE 1.4在語(yǔ)言上提供了一個(gè)新特性,就是assertion(斷言)功能,它是該版本在Java語(yǔ)言方面最大的革新。在軟件開(kāi)發(fā)中,assertion是一種經(jīng)典的調(diào)試、測(cè)試方式,本文將深入解析assertion功能的使用以及其設(shè)計(jì)理念,并給出相關(guān)的例子

assertion(斷言)在軟件開(kāi)發(fā)中是一種常用的調(diào)試方式,很多開(kāi)發(fā)語(yǔ)言中都支持這種機(jī)制,如CC++Eiffel等,但是支持的形式不盡相同,有的是通過(guò)語(yǔ)言本身、有的是通過(guò)庫(kù)函數(shù)等。另外,從理論上來(lái)說(shuō),通過(guò)assertion方式可以證明程序的正確性,但是這是一項(xiàng)相當(dāng)復(fù)雜的工作,目前還沒(méi)有太多的實(shí)踐意義。

在實(shí)現(xiàn)中,assertion就是在程序中的一條語(yǔ)句,它對(duì)一個(gè)boolean表達(dá)式進(jìn)行檢查,一個(gè)正確程序必須保證這個(gè)boolean表達(dá)式的值為true;如果該值為false,說(shuō)明程序已經(jīng)處于不正確的狀態(tài)下,系統(tǒng)將給出警告或退出。一般來(lái)說(shuō),assertion用于保證程序最基本、關(guān)鍵的正確性。assertion檢查通常在開(kāi)發(fā)和測(cè)試時(shí)開(kāi)啟。為了提高性能,在軟件發(fā)布后,assertion檢查通常是關(guān)閉的。下面簡(jiǎn)單介紹一下Javaassertion的實(shí)現(xiàn)。

11) 語(yǔ)法表示

在語(yǔ)法上,為了支持assertionJava增加了一個(gè)關(guān)鍵字assert。它包括兩種表達(dá)式,分別如下:

1.       assert expression1;

2.       assert expression1: expression2;

在兩種表達(dá)式中,expression1表示一個(gè)boolean表達(dá)式,expression2表示一個(gè)基本類(lèi)型或者是一個(gè)對(duì)象(Object) ,基本類(lèi)型包括boolean,char,double,float,intlong。由于所有類(lèi)都為Object的子類(lèi),因此這個(gè)參數(shù)可以用于所有對(duì)象。

12) 語(yǔ)義含義

在運(yùn)行時(shí),如果關(guān)閉了assertion功能,這些語(yǔ)句將不起任何作用。如果打開(kāi)了assertion功能,那么expression1的值將被計(jì)算,如果它的值為false,該語(yǔ)句強(qiáng)拋出一個(gè)AssertionError對(duì)象。如果assertion語(yǔ)句包括expression2參數(shù),程序?qū)⒂?jì)算出expression2的結(jié)果,然后將這個(gè)結(jié)果作為AssertionError的構(gòu)造函數(shù)的參數(shù),來(lái)創(chuàng)建AssertionError對(duì)象,并拋出該對(duì)象;如果expression1值為trueexpression2將不被計(jì)算。

一種特殊情況是,如果在計(jì)算表達(dá)式時(shí),表達(dá)式本身拋出Exception,那么assert將停止運(yùn)行,而拋出這個(gè)Exception

13) 一些assertion例子

下面是一些Assert的例子。

1.       assert  0 < value;

2.       assert  0 < value:"value="+value;

3.       assert  ref != null:"ref doesn't equal null";

4.       assert  isBalanced();

14) 編譯

由于assert是一個(gè)新關(guān)鍵字,使用老版本的JDK是無(wú)法編譯帶有assert的源程序。因此,我們必須使用JDK1.4(或者更新)Java編譯器,在使用Javac命令時(shí),我們必須加上-source 1.4作為參數(shù)。-source 1.4表示使用JDK 1.4版本的方式來(lái)編譯源代碼,否則編譯就不能通過(guò),因?yàn)槿笔〉?/span>Javac編譯器使用JDK1.3的語(yǔ)法規(guī)則。

一個(gè)簡(jiǎn)單的例子如下:

javac      -source   1.4    test.java

15) 運(yùn)行

由于帶有assert語(yǔ)句的程序運(yùn)行時(shí),使用了新的ClassLoaderClass類(lèi),因此,這種程序必須在JDK1.4(或者更高版本)JRE下運(yùn)行,而不能在老版本的JRE下運(yùn)行。

1.       由于我們可以選擇開(kāi)啟assertion功能,或者不開(kāi)啟,另外我們還可以開(kāi)啟一部分類(lèi)或包的assertion功能,所以運(yùn)行選項(xiàng)變得有些復(fù)雜。通過(guò)這些選項(xiàng),我們可以過(guò)濾所有我們不關(guān)心的類(lèi),只選擇我們關(guān)心的類(lèi)或包來(lái)觀察。

§1.1.2                     assertion的設(shè)計(jì)問(wèn)題

首先,我們認(rèn)為assertion是必要的。因?yàn)椋绻麤](méi)有統(tǒng)一的assertion機(jī)制,Java程序通常使用if-then-else或者switch-case語(yǔ)句進(jìn)行assertion檢查,而且檢查的數(shù)據(jù)類(lèi)型也不完全相同。assertion機(jī)制讓Java程序員用統(tǒng)一的方式處理assertion問(wèn)題,而不是按自己的方式處理。另外,如果用戶(hù)使用自己的方式進(jìn)行檢查,那么這些代碼在發(fā)布以后仍然將起作用,這可能會(huì)影響程序的性能。而從語(yǔ)言層次支持assertion功能,這將把assertion對(duì)性能帶來(lái)的負(fù)面影響降到最小。

Java是通過(guò)增強(qiáng)一個(gè)關(guān)鍵字assert實(shí)現(xiàn)支持assertion,而不是使用一個(gè)庫(kù)函數(shù)支持,這說(shuō)明Java認(rèn)為assertion對(duì)于語(yǔ)言本身來(lái)說(shuō)是非常重要的。實(shí)際上,在Java的早期的規(guī)范中,Java是能夠支持assert的,但是由于一些實(shí)現(xiàn)的限制,這些特性從規(guī)范中除去了。因此,assert的再次引入應(yīng)該是恢復(fù)了Java對(duì)assert的支持。C語(yǔ)言就是通過(guò)Assert.h函數(shù)庫(kù)實(shí)現(xiàn)斷言的支持。

Javaassertion的開(kāi)啟也和C語(yǔ)言不太一樣,我們都知道在C語(yǔ)言中,assertion的開(kāi)啟是在編譯時(shí)候決定的。當(dāng)我們使用debug方式編譯程序時(shí)候,assertion被開(kāi)啟,而使用release方式編譯時(shí)候,assertion自動(dòng)被關(guān)閉。Javaassertion卻是在運(yùn)行的時(shí)候進(jìn)行決定的。其實(shí),這兩種方式是各有優(yōu)缺點(diǎn)。如果采用編譯時(shí)決定方式,開(kāi)發(fā)人員將處理兩種類(lèi)型的目標(biāo)碼,debug版本和release版本,這加大了文檔管理的難度,但是提高了代碼的運(yùn)行效率。Java采用運(yùn)行時(shí)決定的方式,這樣所有的assertion信息將置于目標(biāo)代碼中,同一目標(biāo)代碼可以選擇不同方式運(yùn)行,增強(qiáng)目標(biāo)代碼的靈活性,但是它將犧牲因?yàn)?/span>assertion而引起一部分性能損失。Java專(zhuān)家小組認(rèn)為,所犧牲的性能相當(dāng)小,因此java采用了運(yùn)行時(shí)決定方式。

另外,我們注意到AssertionError作為Error的一個(gè)子類(lèi),而不是RuntimeException。關(guān)于這一點(diǎn),專(zhuān)家組也進(jìn)行了長(zhǎng)期的討論。Error代表一些異常的錯(cuò)誤,通常是不可以恢復(fù)的,而RuntimeException強(qiáng)調(diào)該錯(cuò)誤在運(yùn)行時(shí)才發(fā)生的特點(diǎn)。AssertionError通常為非常關(guān)鍵的錯(cuò)誤,這些錯(cuò)誤往往是不容易恢復(fù)的,而且assertion機(jī)制也不鼓勵(lì)程序員對(duì)這種錯(cuò)誤進(jìn)行恢復(fù)。因此,為了強(qiáng)調(diào)assertion的含義,Java專(zhuān)家小組選擇了讓AssertErrorError的子類(lèi)。

§1.1.3                     assertion與繼承

在本節(jié),我們將考慮assertion與繼承的關(guān)系,研究assert是如何定位的。如果開(kāi)啟一個(gè)子類(lèi)的assertion,那么它的父類(lèi)的assertion是否執(zhí)行?

下面的例子將顯示如果一個(gè)assert語(yǔ)句在父類(lèi),而當(dāng)它的子類(lèi)調(diào)用它時(shí),該assertfalse。我們看看在不同的情況下,該assertion是否被處理。

class Base

{

  public void baseMethod()

  { // 總是assertion失敗

    assert      false : "Assertion failed:This is base ";

    System.out.println("Base Method");

  }

}

 

class Derived

  extends Base

{

  public void derivedMethod()

  { // 總是assertion失敗

    assert false: "Assertion failed:This is derive";

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

    }

  }

}

運(yùn)行命令

含義

結(jié)果

Java Derived

不啟用assertion

Base Method
Derived Method

Java -ea Derived

開(kāi)啟所有assertion

Java.lang.AssertionError:Assertion Failed:This is base

Java -da Derived

關(guān)閉所有assertion

Base Method
Derived Method

Java -ea:Base Derived

僅打開(kāi)Baseassertion

Java.lang.AssertionError:Assertion Failed:This is base

Java -ea:Derived Derived

僅打開(kāi)Derivedassertion

Base Method
Java.lang.AssertionError:Assertion Failed:This is derived

從這個(gè)例子我們可以看出,父類(lèi)的assert語(yǔ)句將只有在父類(lèi)的assert開(kāi)啟才起作用,如果僅僅開(kāi)啟子類(lèi)的assert父類(lèi)的assert仍然不運(yùn)行。例如,我們執(zhí)行java -ea:Derived Derived的時(shí)候,Base類(lèi)的assert語(yǔ)句并不執(zhí)行。因此,我們可以認(rèn)為,assert語(yǔ)句不具有繼承功能。

§1.1.4                     assertion的使用

assertion的使用是一個(gè)復(fù)雜的問(wèn)題,因?yàn)檫@將涉及到程序的風(fēng)格,assertion運(yùn)用的目標(biāo),程序的性質(zhì)等問(wèn)題。通常來(lái)說(shuō),assertion用于檢查一些關(guān)鍵的值,并且這些值對(duì)整個(gè)程序,或者局部功能的完成有很大的影響,并且這種錯(cuò)誤不容易恢復(fù)的。assertion表達(dá)式應(yīng)該短小、易懂,如果需要評(píng)估復(fù)雜的表達(dá)式,應(yīng)該使用函數(shù)計(jì)算。以下是一些使用assertion的情況的例子,這些方式可以讓java程序的可靠性更高。

1.       檢查控制流; if-then-elseswith-case語(yǔ)句中,我們可以在不應(yīng)該發(fā)生的控制支流上加上assert false語(yǔ)句。如果這種情況發(fā)生了,assert能夠檢查出來(lái)。
例如:x取值只能使1,2,3,我們的程序可以如下表示

2.                  switch (x)

3.                  { case 1: ;

4.                     case 2: ;

5.                     case 3:

6.                    default: assert false:"x value is invalid: "+x;

7.           }

8.       在私有函數(shù)計(jì)算前,檢查輸入?yún)?shù)是否有效;對(duì)于一私有些函數(shù),要求輸入滿(mǎn)足一些特定的條件,那么我們可以在函數(shù)開(kāi)始處使用assert進(jìn)行參數(shù)檢查。對(duì)于公共函數(shù),我們通常不使用assertion檢查,因?yàn)橐话銇?lái)說(shuō),公共函數(shù)必須對(duì)無(wú)效的參數(shù)進(jìn)行檢查和處理。而私有函數(shù)往往是直接使用的。
例如:某函數(shù)可能要求輸入的參數(shù)必須不為null。那么我們可以在函數(shù)的一開(kāi)始加上 assert parameter1!=null : "paramerter is null in test method";

9.       在函數(shù)計(jì)算后,檢查函數(shù)結(jié)果是否有效;對(duì)于一些計(jì)算函數(shù),函數(shù)運(yùn)行完成后,某些值需要保證一定的性質(zhì),因此我們可以通過(guò)assert檢查該值。
例如,我們有一個(gè)計(jì)算絕對(duì)值的函數(shù),那么我們就可以在函數(shù)的結(jié)果處,加上一個(gè)語(yǔ)句:

assert  value>=0:"Value should be bigger than 0:"+value;

通過(guò)這種方式,我們可以對(duì)函數(shù)計(jì)算完的結(jié)果進(jìn)行檢查。

10.   檢查程序不變量;有些程序中,存在一些不變量,在程序的運(yùn)行生命周期,這些不變量的值都是不變的。這些不變量可能是一個(gè)簡(jiǎn)單表達(dá)式,也可能是一個(gè)復(fù)雜的表達(dá)式。對(duì)于一些關(guān)鍵的不變量,我們可以通過(guò)assert進(jìn)行檢查。
例如,在一個(gè)財(cái)會(huì)系統(tǒng)中,公司的支出和收入必須保持一定的平衡關(guān)系,因此我們可以編寫(xiě)一個(gè)表達(dá)式檢查這種平衡關(guān)系,如下表示。

11.                 private boolean isBalance() {

12.                  ……

13.                }

          

在這個(gè)系統(tǒng)中,在一些可能影響這種平衡關(guān)系的方法的前后,我們都可以加上assert驗(yàn)證:assert isBalance():"balance is destoried";

§1.1.5                     結(jié)論

assertion為開(kāi)發(fā)人員提供了一種靈活地調(diào)試和測(cè)試機(jī)制,它的使用也非常簡(jiǎn)單、方便。但是,如何規(guī)范、系統(tǒng)地使用assertion(特別是在Java語(yǔ)言中)仍然是一個(gè)亟待研究的問(wèn)題