Java 入門

ホーム > Java の基本 > 分かりやすいコードを書くための assert の使い方

分かりやすいコードを書くための assert の使い方

ここでは JDK 1.4 で導入されたアサーション (assertion) の使い方について説明します。

アサーションはデバッグを支援するための機能です。assert ステートメントを使って、記述します。

assert には、常に「真 (true) となると想定している式」を書くことによって開発者が何を想定しているか記載できます。

これがどういう意味か、具体例で示します。

assert の使い方

具体的な例で説明します。次のコードを見てください。

public class AssertTest1 {
  public static void main(String[] args) {
    
    Person p = new Person(15, "Hanako");
    
    int age = p.getAge();
    String name = p.getName();
    
    System.out.println(name.toUpperCase() + " - " + age);

  }
}

最初の行で Person というオブジェクトを作っています。コンストラクタには年齢として 10 を、名前として "Hanako" という文字列を渡しています。 そして getAge メソッド、getName メソッドでそれぞれ、年齢、名前を取得して、それを表示します。

このとき、name を大文字に変換するために toUppserCase() メソッドを呼び出しています。また、年齢は当然ながら0歳以上だと考えています。

プログラムを実行すると次のように、正常動作します。

HANAKO - 15

しかし、Person のコンストラクタで名前として null を渡したらどうなるでしょうか。

> java AssertTest1
Exception in thread "main" java.lang.NullPointerException
        at AssertTest1.main(AssertTest1.java:13)

残念ながらプログラムは NullPointerException が出てプログラムが異常終了してしまいました。

開発者の想定・意図とは?

Person クラスはどうやらコンストラクタに渡した名前の値をそのまま保持していて、getName() メソッドでも単にそれを返してきているようです。

そのため、null を渡せば null が返ってきてしまいます。

名前として返ってきた null に対して toUpperCase() メソッドを呼び出したわけですから、ここで NullPointerException が投げられてしまいます。

つまり、上のコードを良く考えると 「名前が無い人を想定していない (まさか null が返ってくるなんて思って無い)」ということが想定されています。

さらについでに言えば、「負の年齢をもつ人がいると思っていない」という想定もあってもおかしくありません。

だからこそ、getName() の戻り値が null かどうかをチェックせずに toUpperCase() を呼び出しているとか、名前と年齢の区切りに (マイナス記号と同様の) ハイフンを使っていたりするわけです。

このような「開発者の意図」をプログラムに記述するために、次のコードのように assert を使用します。

public class AssertTest1 {
  public static void main(String[] args) {
    
    Person p = new Person(15, "Hanako");
    
    int age = p.getAge();
    String name = p.getName();

assert name != null : "Name = (null)"; assert age >= 0 : "Age is negative.";
System.out.println(name.toUpperCase() + " - " + age); } }

assert には常に真となる(と思っている内容)を記述しておきます。

上記の場合は 「name に null はありえないと思ってます」 「age は 0 以上だと想定している」 ということを書いています。

このように書いてあると、他の開発者がこのコードを見たときに、 「ははぁ、Person を利用している人は null は想定していないのか」と直ちにわかります。

デバッグの支援として assert を活用する

通常実行時に assert は無視されます。

しかしながら、実行時にオプション "-enableassertions" (または省略形の -ea) を渡すと、assert が評価され、false と評価された場合は、 そこで処理が中断します。

上でみたように名前に null を渡した場合の動作は次のようになります。

> java AssertTest1
Exception in thread "main" java.lang.NullPointerException
        at AssertTest1.main(AssertTest1.java:13)

一方、-ea オプションを渡して実行した場合には次のように assert ステートメントが評価され、そこで false となったため、 AssertionError 例外が出て処理が中断しています。

> java -ea AssertTest1
Exception in thread "main" java.lang.AssertionError: Name = (null)
        at AssertTest1.main(AssertTest1.java:10)

C言語などでは通常 assert はマクロとして定義されて、assert 付きのデバッグビルド (あるいはチェックトビルド)、assert などを完全に取り除いたリリースビルド (あるいはフリービルド) という風に区別する場合が多いです。 しかし、Java の assert は実行時のオプションで有効化できます。

Eclipse での -enableassertions の設定

Eclipse 環境で実行する場合に、アサーションを有効化するには次の場所で VM オプションを渡します。

Run メニューから Run Configurations を選択します。

Arguments タブ内の VM arguments に -enableassertions (または -ea) を指定します。

assert 使用上の注意

エラー処理ではないことに注意!

アサートは「エラー処理」ではないので、使い方に気をつけてください。

例えば「ファイルを開く」という動作を行なう場合のことを考えましょう。ご存知の通りファイルを開くという操作は、 開ける場合もあるし、開けない場合も当然想定しなければならないものです。アサートで 「必ずファイルが開けるものと想定した」などと記述するような類のものではありません。

結果をチェックするというようなことは、普通に戻り値をチェックするとか、例外処理をするとかしてください。

assert の有無がプログラムの処理に影響しないように

上述の通り assert はアサーションを有効化したときのみ実行されます。そして可能な限りデバッグに有効に使いたいものです。 実行結果は assert の有無によって変わるようなことがないように気をつけなければなりません。

次の例を見てください。

public class AssertTest1 {
  public static void main(String[] args) {    
    int n = 0;
    assert ++n > 0;
    System.out.println("n = " + n);
  }
}

このコードでは、アサートの有効化の有無によって、最終的な n の値が変わってしまいます。通常実行時 (assert 無効) の場合は n は 0 のまま、 アサーション有効化時には n は 1 となります。

ホーム > Java の基本 > 分かりやすいコードを書くための assert の使い方