未来エンジニア養成所Blog

プログラミングを皆に楽しんでもらうための情報をお届けします。

【Java】アサーション

title


アサーションとは

プログラムには、3つのエラーがあります。

  • コンパイルエラー:文法チェック
  • 実行エラー:実際に実行した時に発生するエラー(実行時例外)
  • 論理エラー:プログラムのコンパイルや実行はできるが、プログラマの意図した結果にならないもの


アサーションとは、プログラマがプログラム完成後には決して起こってはならない論理エラーを、開発中にあらかじめチェックする機能です。

例えば、日付の月フィールドに1から12以外のデータが入ってしまった、年齢フィールドにマイナスデータが入ってしまったといったことは、プログラムの運用を開始する前に、プログラマがきちんと確認をしておかなければなりません。

このようなプログラマでしかわからないエラーを効率的に発見する手段として、アサーションが使われます。


アサーションの構文

  • asset 条件式;
     条件式がfalseの場合、Assersion Errorが発生し、プログラムが終了する。

  • assert 条件式:エラーメッセージ
     条件式がfalseの場合、エラーメッセージを表示して、プログラムを終了する。


Assersion Error


上の例では、assert age >= 0; とアサーションの条件式に指定をすることで、ageフィールドがマイナスデータの時falseを返しアサーションエラーでプログラムが止まるようにしたものです。


Assersion Errorとメッセージ


上の例ではassert age >= 0; とアサーションの条件式がfalseを返したとき、エラーとして表示されるメッセージを記述しています。


【アサーションの利用1】

public class Assert1 {
    public static void main(String[] args) {
        int age = -10;
        assert age >= 0;
        System.out.println("年齢は" + age);
    }
}


[引数]タブの[VM引数]に「-ea」と指定をし[実行]ボタンをクリックします。
※アサーションを有効にして実行するには、実行時に「-ea」(enableassertions)を指定します。

実行結果


アサーションエラーを確認します。

Exception in thread "main" java.lang.AssertionError
    at Assert1.main(Assert1.java:4)


ageフィールドに正の数値を入れて、アサーションエラーにならないことを確認します。

public class Assert1 {
    public static void main(String[] args) {
        int age = 10;
        assert age >= 0;
        System.out.println("年齢は" + age);
    }
}


実行結果

年齢は10


4行目のassert age >= 0; の記述により、ageフィールドの値が0以上でなければfalseが返り、アサーションエラーが発生してプログラムが終了します。

プログラマはageフィールドにマイナスデータが入った場合はエラーメッセージを表示して正しい値を再入力させるような手段を講じる必要があります。


アサーション実行時のオプション
アサーションを実行したい場合には、トレーニングで行ったように、クラス実行時にVM引数に「-ea」(enableassertions)オプションを付けて実行します。

このオプションを指定しないでクラスを実行するとアサーションの記述は無視されます。

また「-da」(disableassetions)はアサーションを無視するオプションです。

実行時にVM引数にこのオプションを付けることでも、アサーションを無視することができます。


【アサーションの利用2】

public class Assert2 {
    public static void main(String[] args) {
        int age = -10;
        assert age >= 0 : "年齢にはマイナスデータは入れられません!";
        System.out.println("年齢は" + age);
    }
}


[実行構成]で[引数]タブの[VM引数]に「-ea」を指定して、Assset2クラスを実行します。

Exception in thread "main" java.lang.AssertionError: 年齢にはマイナスデータは入れられません!
    at Assert2.main(Assert2.java:4)


4行目のassert条件式の後ろにコロンに続けて、独自のエラーメッセージを記述することができます。

なぜアサーションエラーになったかなどを、エラーメッセージを通して明示しておくと、修正がしやすく、効率的にデバッグができます。


アサーションを利用する場面

アサーション機能は「ある条件が成立すべきことをチェックする」ための処理を挿入しておき、その条件に違反している場合はアサーションエラーを出すことによって動作をチェックするものです。

次のような場面でアサーションを利用すると効率の良いチェックができます。


  • privateメソッドの事前条件

    • メソッドが呼び出されたとき、渡される引数の値が正しいかどうかをチェックする
    • この条件がアサーションエラーになると、メソッド呼び出し前の処理に誤りがあることを表す
  • メソッドの事後条件

    • メソッドが正常に実行された後に満たしていなければならない値かどうかをチェックする
    • この条件がアサーションエラーになると、メソッド内の処理に誤りがあること表す
  • メソッドの不変条件

    • メソッド内で常に成立しなければならない条件をチェックする


【アサーションの有効な利用方法1】

public class Assert3 {
    private static String foo(int x){
        assert x >= 0 : x + "は不正な値です";
        return "正常終了しました";
    }

    public static void main(String[] args) {
        System.out.println(foo(100));
        System.out.println(foo(-100));
    }
}


VM引数に「-ea」を指定して、Assert3クラスを実行します。

正常終了しました
Exception in thread "main" java.lang.AssertionError: -100不正な値です
    at Assert3.foo(Assert3.java:3)
    at Assert3.main(Assert3.java:10)

※処理のタイミングによって、エラーメッセージが先に表示される場合もあります。


2行目~5行目にprivateなstaticメソッドを定義し、その引数に渡された値を、アサーション機能を使ってチェックしています。

8行目で呼び出したfoo()の引数には100が指定されており、アサーションエラーにはならずに正常終了しています。

9行目で呼び出したfoo()の引数には-100が指定されており、アサーションエラーが発生しています。

このようにメソッドの引数に渡される値が条件を満たしているかどうかをアサーションでチェックしているわけですが、なぜprivateメソッドに有効なのでしょうか。

なぜpublicメソッドには有効でないのでしょうか。

privateメソッドは自分のクラス内からよびだすことができるメソッドです。

つまり、このクラスを記述したプログラマ自身が開発段階で、メソッドの引数に渡される値を管理できます。

ところが、publicメソッドは他のプログラマが書いた処理からも呼び出される可能性があり、外部からはどのような引数が渡されてくるのかプログラマは予測できません。

予測できないものをチェックしようがないのです。

開発段階のアサーションでのチェックはできなので、実際に運用され利用された時に例外処理によってエラー処理を行うようにします。

したがって、メソッドの引数の値をアサーションでチェックするのはprivateメソッドのみ有効と言えるわけです。


【アサーションの有効な利用方法2】

public class Assert4 {
    private static int foo(int x){
        return x * 100;
    }

    public static void main(String[] args) {
        int ans = 0;
        ans = foo(100);
        System.out.println(ans);
        ans = foo(-100);
        System.out.println(ans);

        assert ans > 0 : ans + "は不正な値です";
    }
}


VM引数に「-ea」を指定して、Assert4クラスを実行します。

10000
-10000
Exception in thread "main" java.lang.AssertionError: -10000は不正な値です
    at Assert4.main(Assert4.java:13)


2行目~4行目にメソッドを定義し、8行目と10行目でそのメソッドを呼び出しています。

メソッドの戻り値を変数ansに格納し、13行目でansの値をアサーションチェックしています。

このようにアサーションエラーになった場合は、メソッドの内の処理に誤りがあるということで、プログラムコードを修正する必要があります。

この、メソッドの事後処理チェックではメソッドの修飾子はprivateでもpublicでも構いません。

なぜなら、このメソッドの結果をチェックするものであり、このプログラムを開発したプログラマがメソッドの処理を管理しているからです。


アサーションの有効な利用方法3

public class Assert5 {
    static void foo(int month){
        switch(month){
            case 1: System.out.println("1");break;
            case 2: System.out.println("1");break;
            case 3: System.out.println("1");break;
            case 4: System.out.println("1");break;
            case 5: System.out.println("1");break;
            case 6: System.out.println("1");break;
            case 7: System.out.println("1");break;
            case 8: System.out.println("1");break;
            case 9: System.out.println("1");break;
            case 10: System.out.println("1");break;
            case 11: System.out.println("1");break;
            case 12: System.out.println("1");break;
            default:assert false;
        }
    }

    public static void main(String[] args) {
        foo(13);
    }
}


VM引数に「-ea」を指定して、Assert5クラスを実行しましょう。

Exception in thread "main" java.lang.AssertionError
    at Assert5.foo(Assert5.java:16)
    at Assert5.main(Assert5.java:21)


月データは1~12までの値が有効で、それ以外の値が入った場合は月データとして無効です。

switch文の引数で無効な値が入った場合は、defaultブロックに制御が移り、条件式にfalseが指定してあるので、無条件にアサーションエラーが発生します。

このようにあるフィールドの値が一定の条件を満たしているかどうかを、アサーションでチェックします。


アサーションの不適切な使用例

次のアサーションでチェックすべきでない例を見ていきましょう。

  • publicメソッドの引数のチェック

    • publicメソッドの引数やコマンドライン引数は、運用段階の実行時でないと、どのような値がはいるかわからないので、アサーションでチェックすべきでない。
  • コマンドライン引数のチェック

    • アサーションチェックは開発時に行うものであり、実際に運用する際にはアサーションチェックは行わない。
    • コマンドライン引数は、実際に運用する時に指定するものなので、コマンドライン引数に対するチェックはアサーションでは行わない。
    • 運用時に与えられた値は、どのようにその値が不適切なのかをより具体的に例外を投げる。
      (IllegalArgumemtException、IndexOutOfBoundsException、NullPointerExceptionなど)
  • アサーションの有効/無効により処理内容が変化する場合

    • アサーション式は開発段階である条件が成立しているかどうかをチェックするもの。
    • アサーション式に書かれているものは、実行時に必ず実行されるかどうかはわからない。
      (実行時にオプションで、有効/無効を指定する)
    • 実行時にアサーションが無効になってしまうと、アサーション式に書かれたものは実行されない。
    • 必ず実行されなければならない処理をアサーションに書くと、実行されない可能性がある。


【アサーションの不適切な使用例1】

public class Assert6 {
    public static void main(String[] args) {
        if(args.length != 5){
            System.out.println("コマンドライン引数の数が不正です");
            throw new ArrayIndexOutOfBoundsException();
        }
    }
}


[プログラムの引数]に「a b c d e f g h i 」と入力をし、Assert5クラスを実行します。

コマンドライン引数の数が不正です
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
    at Assert6.main(Assert6.java:5)


コマンドライン引数に指定する数を5個以外にして実行しています。

コマンドライン引数の数が必ず5個でなければならないとき、5個以外の数のコマンドライン引数が指定されたら、例外を発生させるようにしています。

コマンドライン引数は、クラスの実行時に指定するものであり、開発時には何個指定されるかはわからないため、このようなチェックにはアサーションは使わないようにします。


【アサーションの不適切な使用例2】

public class Assert7 {
    public static void main(String[] args) {
        int x = 0;
        assert x++ == 0;
        if(x == 0){
            System.out.println("xの値は0です");
        }else{
            System.out.println("xの値は1です");
        }
    }
}


アサーションを有効にして(VM引数に-eaを指定)Asset7クラスを実行します。

xの値は1です


アサーションを無効にして(VM引数に-daを指定)Asset7クラスを実行します。

xの値は0です


このプログラムにおいて変数xは必ずインクリメントされなければならないものとします。

xのインクリメント処理はassert式の中に書かれています。

アサーションが有効である場合には、アサーションが実行されるのでxはインクメントされますが、アサーションが無効の場合にはアサーションが無視されるので、xはインクリメントされません。

このようにアサーションの有効/無効で処理結果がかわってしまうので、必ず実行したい処理をアサーションの中に記述することは、不適切な使い方です。


まとめ

  • アサーション

    • プログラマがプログラム完成後には決して起こってはならない論理エラーを、開発中にあらかじめチェックする機能。
    • assert  条件式;
    • assert  条件式:エラーメッセージ;
  • アサーションを利用する場面

    • privateメソッドの事前条件。
    • メソッドの事後条件。
    • メソッドの不変条件。
  • アサーションの不適切な使用例

    • publicメソッドの引数のチェック。
    • コマンドライン引数のチェック。
    • アサーションの有効/無効により処理内容が変化する場合。


参考図書



独学で挫折しそうになったら、オンラインプログラミングスクール
未来エンジニア養成所Logo



あわせて学習したい

phoeducation.work phoeducation.work