Go to Previous Page Go to Contents Go to Java Page Go to Next Page
New Features of Java2 SDK, Standard Edition, v1.4
 
 

Assertion Facility

 
 

Assertion ってなに?

 
 

アプリケーションを作っているときに、特にデバッグを行っているとき、いろいろとデバッグのためにプリント文を入れることがありますね。例えば、次のような時です。

    if(x == 0){
        foo();
    }else if(x > 0){
        bar();
    }else{
        System.out.pritnln("ほんとはここにはこないはず x = " + x);
    }

赤で示した部分はデバッグのために使用しています。本来なら、x は負の数 にならないはずなんだけど、どこかが間違っているので負になっちゃうことを教えてくれるわけですね。

個人的に使っているアプリケーションなら、このようなデバッグのためのコードを入れたままにしておいてもいいかもしれませんが、できればアプリケーションをリリースするときには必要がないコードになります。

こんなときに Assertion が使用できます。

つまり、Assertion はデバッグ版の時だけ、式を評価するための機構になります。C/C++ でも同様の機構があります。こちらは NDEBUG がマクロ定義されているかどうかで、実行されたりされなかったりします。

なんだこれだけかと思わないでください。Assertion は API ではなく、言語自体の文法の拡張になるのです。文法の変更という点では Assertion と Java 2 SE, v1.5 で導入が予定されている Generics が最後になると James Gosling 氏が JavaOne 2001 の Keynote Speech で述べています。

せっかくの機能なので、有効にデバッグにいかしましょう。

ところで、Assertion 自体は API ではないので、今回はサンプルプログラムは特にありません。ほとんどプログラムの断片みたいなものですが、御了承ください。

 

 
  どうやって書くか  
 

書き方はとても簡単で、単に次のように書けばいいだけです。

    assert 式1;
 
        or
 
    assert 式1 : 式2;

式1 には boolean の値になるようにします。また、式 2 はプリミティブもしくは文字列にします。

例えば上記の例だと

    if(x == 0){
        foo();
    }else if(x > 0){
        bar();
    }else{
        assert x >= 0;
    }

と書くことができます。assert 文の式1 の値が false の時に、AssertionError が発生します。

式2 が入った場合だと

    if(x == 0){
        foo();
    }else if(x > 0){
        bar();
    }else{
        assert x >= 0 : x;
    }

とか

    if(x == 0){
        foo();
    }else if(x > 0){
        bar();
    }else{
        assert x >= 0 : "ほんとはここにはこないはず";
    }

のように記述できます。

式2 がある場合は、AssertionError と一緒に式2 が出力されます。

これからの例で使うためのクラスをここで作っておきましょう。クラス名は AssertionTest にしました。

public class AssertionTest {
    public AssertionTest(int value){
        assert value == 0: value;
    }
 
    public static void main(String[] args){
        try {
            new AssertionTest(Integer.parseInt(args[0]));
        } catch(NumberFormatException ex){}
    }
}

いちおう、ダウンロードもできるようにしておきます。

アプリケーションのソース AssertionTest.java

 

 
 

次はコンパイル

 
 

Assertion を入れるために Java2 SDK, v1.4 からクラスファイルの構造が変更されました。v1.4 形式のクラスファイルを作るためには、-source オプションを使用します。-souce オプションには v1.4 の場合は 1.4、v1.3.x の場合は 1.3 を指定します。

例えば、assert 文を含んだ AssertionTest.java をコンパイルする場合は次のように行います。

C:\temp>javac -source 1.4 AssertionTest.java

-souce オプションのデフォルトは 1.3 なので、-source オプションを指定しないと次のようにコンパイルエラーが出てしまいます。

C:\temp>javac AssertionTest.java
AssertTest.java:3: 警告: リリース 1.4 では assert はキーワードなので識別子
として使うことはできません。
        assert x == 0;
        ^
AssertTest.java:3: 文ではありません。
        assert x == 0;
        ^
AssertTest.java:3: ';' がありません。
        assert x == 0;
               ^
エラー 2 個
警告 1 個
 
C:\temp>

 

 
 

Assertion の使い方

 
 

コンパイルができたので、さっそく実行してみましょう。

単に実行すると、assert 文は無視されてしまいます。Assertion を有効にするには次のように実行します。

java [ -enableassertion | -ea ] [ :<package name>"..." | :<class name> ]

Assertion のドキュメントには書いてあるのですが、分かりにくいですね。-ea もしくは -enableassertion というオプションを使用することで Assertion が有効になることは分かりますが...

同じように Assertion を無効にするには次のように実行します。

java [ -disableassertion | -da ] [ :<package name>"..." | :<class name> ]

両方とも分かりにくいので、いろいろなパターンについてまとめたものを次表に示しました。

実行法 説明
java -ea AssertionTest
すべてのクラス (システムは除く) の Assertion が有効
java -ea:... AssertionTest
カレントディレクトリのパッケージ化していないクラスに対して Assertion が有効
java -ea:jp.gr.java_conf.skrb... AssertionTest
jp.gr.java_conf.skrb パッケージおよびサブパッケージに属するクラスに対して Assertion が有効
java -ea:jp.gr.java_conf.skrb... -ea:javax.swing... AssertionTest
jp.gr.java_conf.skrb パッケージおよびそのサブパッケージ、また javax.swing パッケージおよびそのサブパッケージに対して Assertion が有効
java -ea:javax.swing.JFrame AssertionTest
javax.swing.JFrame クラスの Assertion が有効
java -ea:java.awt.Frame -ea:javax.swing.JFrame AssertionTest
java.awt.Frame クラスと javax.swing.JFrame クラスの Assertion が有効
java -ea:java.awt... -ea:javax.swing.JFrame AssertionTest
java.awt パッケージとそのサブパッケージおよび javax.swing.JFrame の Assertion が有効
java -da AssertionTest
すべてのクラス (システムは除く) の Assertion が無効
java -da:... AssertionTest
カレントディレクトリのパッケージ化していないクラスに対して Assertion が無効
java -da:jp.gr.java_conf.skrb... AssertionTest
jp.gr.java_conf.skrb パッケージおよびサブパッケージに属するクラスに対して Assertion が無効
java -da:jp.gr.java_conf.skrb... -da:javax.swing... AssertionTest
jp.gr.java_conf.skrb パッケージおよびそのサブパッケージ、また javax.swing パッケージおよびそのサブパッケージに対して Assertion が無効
java -da:javax.swing.JFrame AssertionTest
javax.swing.JFrame クラスの Assertion が無効
java -da:java.awt.Frame -da:javax.swing.JFrame AssertionTest
java.awt.Frame クラスと javax.swing.JFrame クラスの Assertion が無効
java -da:java.awt... -da:javax.swing.JFrame AssertionTest
java.awt パッケージとそのサブパッケージおよび javax.swing.JFrame の Assertion が無効
java -ea -da:javax.swing.JFrame AssertionTest
javax.swing.JFrame 以外の Assertion が有効
java -ea:javax.swing... -da:javax.swing.JFrame AssertionTest
javax.swing.JFrame 以外の javax.swing パッケージおよびそのサブパッケージの Assertion が有効

-da オプションは単独で使うことはほとんどなく、-ea オプションと組み合わせて使うのだと思います。

それではさっそく AssertionTest を実行してみましょう。

まずはオプションを指定しないで、Assertion が無効になることを確認。

C:\temp>java AssertionTest 3
 
C:\temp>

次に -ea オプションを使用してみます。

C:\temp>java -ea AssertionTest 3
 Exception in thread "main" java.lang.AssertionError: 3
        at AssertionTest.<init>(AssertionTest.java:3)
        at AssertionTest.main(AssertionTest.java:8)

C:\temp>

こんなふうに、assert 文が実行されて AssertionError が発生します。次に引数を 0 にしてみましょう。

C:\temp>java -ea AssertionTest 0

C:\temp>

assert 文の式の評価が true であれば AssertError は発生しません。AssertionTest.java では

        assert value == 0: value;

のように value が 0 であれば true になるので、AssertionError が発生しなかったのです。

ところで、assert 文の書き方は次のようになりますが、式2 がないときを試してみましょう。

    assert 式1;
 
        or
 
    assert 式1 : 式2;

式2 がない形に AssertionTest.java を変更して実行してみました。

    public AssertionTest(int value){
//      assert value == 0 : value;     // コメントアウト
        assert value == 0;
    }

実行結果はこうなります。違いが分かりますか?

C:\temp>java -ea AssertionTest 3
 Exception in thread "main" java.lang.AssertionError
        at AssertionTest.<init>(AssertionTest.java:1)
        at AssertionTest.main(AssertionTest.java:8)

C:\temp>

2 行目の AssertionError の後に value の値が出力されていないことが異なる部分です。式 2 の内容が出力されると書きましたが、実際にやってみないとよく分からないですね。

 

 
 

どこでどうやって使うか

 
 

それが一番の問題です ^^;;

簡単に想像がつくのは if 文や switch 文などの分岐に使用する場合です。

    if(x == 0){
        foo();
    }else if(x > 0){
        bar();
    }else{
        assert false;
    }

assert false; と書いてしまえば、assert 文が実行されるときには常に AssertionError が発生します。

同じように switch 文でも使えます。たとえば、すべての分岐を網羅した case 文が書いておいて、default に assert 文を入れておきます。本来ならどこかの case 文に相当するのでしょうが、何らかの間違ったデータが入っていたときに default に飛んで AssertionError が発生されるということです。

    switch(x){
      case NORTH:
        goNorth();
        break;
      case SOUTH:
        goSouth();
        break;
      case EAST:
        goEast();
        break;
      case WEST:
        goWest();
        break;
      defaults:
        assert false : x;
    }

もちろん、関数などでも使えます。例えば、foo というメソッドがコールされるときには、プロパティ x が null ではいけないという条件があるとします。そんなときには

    private SomeClass x;
 
    public foo(){
      assert x == null;
 
      x.doSomething();
    }

とでもしておけば、null だった場合には AssertionError が発生します。

Assertion がないときに、筆者はデバッグのためにこんなコードを書くことがありました。

    public foo(){     
      (new Exception()).printStackTrace();
 
      x.doSomething();
    }

別に Exception をスローするわけではないのですが、こうすると foo がコールされるとスタックトレースが出力されるので、どこから呼ばれているかを確かめることができるのです。もちろん、リリース時にはこのコードは抜いてしまいます。

でも、Assertion があれば、これの代わりにすることができますし、リリース版でも assert 文を削除する必要はありません。

Effel というオブジェクト指向言語を開発した Meyer さんによれば、アサーションは事前条件、事後条件、invaliant からなるそうです。事前条件はある処理を行う前に成立していなければならない条件、事後条件は処理を行った後に成立していなければならない条件、invaliant は常に成立していなければならない条件です。

先ほどの、if 文や switch 文で Assertion を使用した例は invaliant に相当します。また、foo メソッドの例は事前条件です。

ここまでは、Assertion をデバッグに使うということで説明を加えてきましたが、同じく Meyer さんによれば Assertion には次のような用途が間がられると言っています。

  1. 正しい動作を行うアプリケーションを記述するため
  2. ドキュメントの代わりとして
  3. デバッグ
  4. フォルトトレラント

1 は事前条件や事後条件などをきちんと記述しておくことで、アプリケーションの動作を正しく定義しておくことができることからきています。2 も 1 と同様にアプリケーションの動作を定義できるので、これをドキュメント代わりに使えるということです。

4 のフォルトトレラントは障害耐久性を高めるためです。アプリケーションには通常の例外処理では拾えないエラーってありますよね。例えば、変数 x は 0 以上でなければ正しく処理できないけど、負の値でもとりあえず処理はできてしまう (もちろん結果は間違っているんですが) なんてことが。

こんなときに Assertion を使って、AssertionError の例外処理でエラーの対処をすることもできます。

こんなふうにいろいろな場所で Assertion は使用することができます。アプリケーションを効率よく開発するために、うまく Assertion を使ってみてください。

ただし、容易に想像できるのですが、Assertion の使いすぎはパフォーマンスを落とします。-ea オプションが使用されていないときでも、assert 文を解釈するために時間がかかるためある程度はパフォーマンス低下は避けられません。まぁ、たいていはこのくらいのパフォーマンスダウンは問題にならないと思いますが...

適度に使って、効率よくアプリケーションの開発をしましょう。

参考 URL

(Jul. 2001)

 
 
Go to Previous Page Go to Contents Go to Java Page Go to Next Page