Java

アサーションを使用したプログラミング

ドキュメントの目次

「アサーション」は、プログラムに関する前提をテストできる Java TM プログラミング言語の文です。 たとえば、粒子の速度を計算するメソッドを記述した場合に、計算される速度が光速よりも遅いことを前提とすることがあります。

各アサーションは、アサーションが実行されたときに true になると想定される boolean 式を含んでいます。 true にならない場合は、システムによってエラーがスローされます。 アサーションは、boolean 式が true であることを確認することによって、プログラムの動作に関する前提を検証します。これによって、プログラムにエラーがない可能性が高くなります。

プログラミング中にアサーションを記述すると、すばやくかつもっとも効果的にバグを発見して修正できることが経験的に実証されています。 さらに、アサーションはプログラムの内部的な動作の文書化に役立つので、保守が容易になるという利点もあります。

このドキュメントでは、アサーションを使ったプログラミング方法について説明します。 次のトピックについて説明します。

 


はじめに

アサーション文は 2 つの形式で記述できます。 最初に単純な形式を示します。

    assert Expression1 ;
Expression1 は、boolean 式です。 アサーションは、システムによって実行されると、Expression1 を評価し、結果が false の場合は、詳細メッセージを表示しないで AssertionError をスローします。

次に、アサーション文の 2 つ目の形式を示します。

    assert Expression1 : Expression2 ;

説明

この形式の assert 文は、AssertionError の詳細メッセージを提供するために使用します。 システムが Expression2 の値を適切な AssertionError コンストラクタに渡し、コンストラクタは値の文字列表現をエラーの詳細メッセージとして使用します。

詳細メッセージの目的は、アサーションの失敗の詳細を把握して伝達することです。 このメッセージを参照して、アサーションの失敗の原因となったエラーを診断し、最終的にはエラーを解決できるようにする必要があります。 詳細メッセージは、ユーザレベルのエラーメッセージではないため、一般的に、そのままで理解できるメッセージにしたり、国際化したりする必要はありません。 詳細メッセージは、失敗したアサーションを含むソースコードと組み合わせて、スタックトレース全体のコンテキスト内で解釈されます。

すべてのキャッチされない例外と同じように、一般的にスタックトレース内のアサーション失敗には、スロー元のファイルと行番号のラベルが付けられます。 失敗の診断に役立つ追加情報をプログラムが提供できる場合にのみ、アサーション文の最初の形式よりも、2 番目の形式を優先的に使用します。 たとえば、Expression1 に、2 つの変数 xy の関係が含まれる場合は、2 番目の形式を使用する必要があります。 このような状況では、Expression2 として "x: " + x + ", y: " + y のような式がよく使われます。

場合によっては、Expression1 の評価に時間がかかることがあります。 たとえばソートされていないリスト内の最小要素を検索するメソッドを記述し、選択された要素が確かに最小かどうかを確認するためのアサーションを追加するとします。 アサーションによって実行される処理には、少なくともメソッド自体によって実行される処理と同じ時間がかかります。 配備後のアプリケーションのパフォーマンスにアサーションが影響しないようにするために、プログラムの起動時にアサーションを有効または無効にすることができます。デフォルトではアサーションは無効になります。 アサーションを無効にすると、パフォーマンスに対する影響が完全になくなります。 無効になったアサーションは、セマンティクスおよびパフォーマンスの観点から見ると、基本的に空文と同じです。 詳細については「アサーションの有効化および無効化」を参照してください。

Java プログラミング言語への assert キーワードの追加によって既存のコードが影響を受けます。 詳細については、「既存のプログラムとの互換性」を参照してください。

 


コードへのアサーションの挿入

アサーションの使用が役に立つ状況は数多くあります。 このセクションでは、それらの状況についていくつか説明します。

また、いくつかの状況ではアサーションを使用しないようにする必要があります。

 

内部の不変条件

アサーションが登場するまで、多くのプログラマは、コメントを使用してプログラムの動作に関する前提を示していました。 たとえば、if 文内の else 句に関する前提を説明するために、次のようなコードを記述したとします。

    if (i % 3 == 0) {
        ...
    } else if (i % 3 == 1) {
        ...
    } else { // We know (i % 3 == 2)
        ...
    }

現在では、不変条件を表明するコメントを記述したときには常にアサーションを使用する必要があります。 たとえば上の if 文は次のように記述する必要があります。

    if (i % 3 == 0) {
        ...
    } else if (i % 3 == 1) {
        ...
    } else {
        assert i % 3 == 2 : i;
        ...
    }

上の例のアサーションは、i が負の場合、失敗します。% 演算子は、真のモジュラス演算子ではありませんが、剰余が発生したときに、負になる可能性があります。

アサーションは、デフォルトの case がない switch 文でも使用します。デフォルトの case がないことは、一般的に、プログラマがいずれかの case が常に実行されると確信していることを示します。 特定の変数が少ない数の値のいずれかになるという前提は、アサーションを使用してチェックする必要がある不変条件です。 たとえば、次の switch 文が、トランプのカードを扱うプログラム内で使用されているとします。

    switch(suit) {
      case Suit.CLUBS:
        ...
        break;

      case Suit.DIAMONDS:
        ...
        break;

      case Suit.HEARTS:
        ...
        break;

      case Suit.SPADES:
        ...
    } 

このコードはおそらく、suit 変数が 4 つの値のいずれかになるという前提を示しています。 この前提をテストするには、次のデフォルトの case を追加します。

      default:
        assert false : suit; 

アサーションが有効になっているときに suit 変数が別の値を取ると、アサーションは失敗し、AssertionError がスローされます。

代わりに次のコードを使用できます。

      default:
        throw new AssertionError(suit); 
このコードはアサーションが無効になっている場合でも保護機能を提供し、しかも保護を追加しても負荷は増加しません。 これは、プログラムが失敗しない限り、throw 文が実行されないためです。 さらに、このコードは、assert 文が使用できないような状況でも有効です。 包含するメソッドが値を返し、switch 文内の各 case が return 文を含み、さらに switch 文のあとに return 文がない場合、アサーションを使用してデフォルトの case を追加すると構文エラーになります。 一致する case がなくアサーションが無効になっている場合、メソッドは値を返さずに復帰します。

 

制御フローの不変条件

前の例は、不変条件をテストするだけでなく、アプリケーションの制御フローに関する前提もチェックします。 元の switch 文の作成者はおそらく、suit 変数が常に 4 つの値のいずれかを取ることだけでなく、4 つの case のいずれかが常に実行されることも前提としています。 このことは、アサーションが一般的に使用される別の領域を示しています。 すなわち、アサーションは到達しないと予想される場所に配置します。次のアサーション文を使用します。

    assert false; 

たとえば、次のようなメソッドを想定します。

    void foo() {
        for (...) {
            if (...)
                return;
        }
        // Execution should never reach this point!!!
    } 

次のコードのように最後のコメントを置き換えます。

    void foo() {
        for (...) {
            if (...)
                return;
        }
        assert false; // Execution should never reach this point!
    } 


注: この技法は、注意して使用してください。 Java 言語仕様 ( JLS 14.20) に定義されているように文が到達しない場合は、到達不可能であることを表明しようとすると、コンパイル時にエラーが発生します。 ここでも、単純に AssertionError をスローするコードを代わりに使用できます。

 

事前条件、事後条件、およびクラスの不変条件

assert 構文は、契約による設計を完全に適用した機能ではありません。ただし、非公式な、契約による設計スタイルのプログラミングは、支援できます。 ここでは、次の目的でアサーションを使用する方法を説明します。

 

事前条件

規約により、public メソッドの事前条件は、特定の指定された例外をスローする明示的なチェックによって適用されます。 例を示します。

    /**
     * Sets the refresh rate.
     *
     * @param  rate refresh rate, in frames per second.
     * @throws IllegalArgumentException if rate <= 0 or
     *          rate > MAX_REFRESH_RATE.
     */
     public void setRefreshRate(int rate) {
         // Enforce specified precondition in public method
         if (rate <= 0 || rate > MAX_REFRESH_RATE)
             throw new IllegalArgumentException("Illegal rate: " + rate);

         setRefreshInterval(1000/rate);
     } 

この規約は、assert 構文を追加しても影響を受けません。 public メソッドのパラメータのチェックにはアサーションを使用しないでください。 public メソッドは、常に引数チェックを適用することを保証するので、assert は適していません。 public メソッドは、アサーションが有効かどうかにかかわらず引数をチェックする必要があります。 さらに、assert 構文は、指定した種類の例外をスローしません。 assert 構文がスローできるのは、AssertionError のみです。

ただし、クライアントがクラスを使用して行う処理の内容にかかわらず true になることがわかっている private メソッドの事前条件については、アサーションを使用してその事前条件をテストできます。 たとえば、前述のメソッドによって呼び出される次の「ヘルパー メソッド」内ではアサーションの使用が適しています。

   /**
    * Sets the refresh interval (which must correspond to a legal frame rate).
    *
    * @param  interval refresh interval in milliseconds.
    */
    private void setRefreshInterval(int interval) {
        // Confirm adherence to precondition in nonpublic method
        assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;

        ... // Set the refresh interval
    } 

上の例のアサーションは、MAX_REFRESH_RATE が 1000 より大きくなり、ユーザが 1000 を超えるリフレッシュレートを選択すると、失敗します。 つまり、ライブラリ内にバグが存在しています。

 

ロックステータス事前条件

マルチスレッド化による使用を目的として設計されたクラスは、しばしば、ロックの有無に関する事前条件を使用する private メソッドを含んでいます。 たとえば、次のようなコードがよく使われます。

   private Object[] a;

   public synchronized int find(Object key) {
      return find(key, a, 0, a.length);
   }

   // Recursive helper method - always called with a lock on this object
   private int find(Object key, Object[] arr, int start, int len) {
       ...
   } 

holdsLock という static メソッドが Thread クラスに追加されました。このメソッドは、現在のスレッドが指定されたオブジェクトをロックしているかどうかをテストします。 このメソッドを assert 文と組み合わせて使用すると、次の例に示すように、ロックステータス事前条件を説明するコメントを補足することができます。

    // Recursive helper method - always called with a lock on this.
    private int find(Object key, Object[] arr, int start, int len) {
        assert Thread.holdsLock(this); // lock-status assertion 
        ...
    } 

特定のロックが保持されていないことを表明するロックステータスアサーションを記述することもできます。

 

事後条件

事後条件は、public メソッドと public ではないメソッドの両方で、アサーションを使用してテストできます。 たとえば、次の public メソッドは、assert 文を使用して事後条件をチェックします。

    /**
     * Returns a BigInteger whose value is (this-1 mod m).
     *
     * @param  m the modulus.
     * @return this-1 mod m.
     * @throws ArithmeticException  m <= 0, or this BigInteger
     *         has no multiplicative inverse mod m (that is, this BigInteger
     *         is not relatively prime to m).
     */
    public BigInteger modInverse(BigInteger m) {
        if (m.signum <= 0)
            throw new ArithmeticException("Modulus not positive: " + m);

        ... // Do the computation

        assert this.multiply(result).mod(m).equals(ONE) : this;
        return result;
    }

事後条件をチェックするために、計算を実行する前に一部のデータの保存が必要なことがあります。 データの保存は、2 つの assert 文と、1 つ以上の変数の状態を保存する単純な内部クラスを使用して行うことができます。データを保存しておけば、計算の後にチェック (または再チェック) することができます。 たとえば、次のようなコードを想定します。

    void foo(int[] array) {
        // Manipulate array
        ...

        // At this point, array will contain exactly the ints that it did
        // prior to manipulation, in the same order.
    }

次に、上のメソッドを変更して、事後条件の文字列のアサーションを関数のアサーションに変換します。

    void foo(final int[] array) {

        // Inner class that saves state and performs final consistency check
        class DataCopy {
            private int[] arrayCopy;

            DataCopy() { arrayCopy = (int[]) array.clone(); }

            boolean isConsistent() { return Arrays.equals(array, arrayCopy); }
        }

        DataCopy copy = null;

        // Always succeeds; has side effect of saving a copy of array
        assert ((copy = new DataCopy()) != null);

        ... // Manipulate array

        // Ensure array has same ints in same order as before manipulation.
        assert copy.isConsistent();
     } 

ここでは、複数のデータフィールドを保存して、複数のアサーションを任意の場所で使用して計算前および計算後の値を検証する方法について、ごく一般的に示しています。

最初の assert 文は、もっぱらその目的以外の処理のために実行されていますが、次のようなより表現的な式に置き換えたくなるかもしれません。

    copy = new DataCopy(); 

このような置き換えは行わないでください。 上の文は、アサーションが有効であるどうかにかかわらず配列をコピーするため、アサーションを無効にしたときにほかの処理に影響を及ぼさないという、アサーションの使用原則に違反します。

 

クラスの不変条件

クラスの不変条件は、内部不変条件の一種で、常にクラスのすべてのインスタンスに適用されます。ただし、インスタンスが 1 つの安定した状態から別の状態に移行しているときには適用されません。 クラスの不変条件は、複数の属性間の関係を指定することができ、またメソッドの完了前と完了後に true になっている必要があります。 たとえば、バランスツリーのデータ構造を実装することを想定します。 ツリーのバランスと順序が適切になっていることがクラスの不変条件だとします。

アサーションは、特に内部不変条件のチェック向けに設計されているわけではありません。 場合によっては、複数の式を組み合わせて必要な制約をチェックしてから、アサーションから呼び出せる単一内部メソッドに渡すと便利なことがあります。 バランスツリーの例では、データ構造の記述に従ってツリーが効率的に構築されていることをチェックする private メソッドを実装することが適切な場合もあります。

    // Returns true if this tree is properly balanced
    private boolean balanced() {
        ...
    }

このメソッドは、メソッドの完了前と完了後に true になっている必要がある制約をチェックするので、各 public メソッドとコンストラクタは、復帰の直前に次の行を含んでいる必要があります。

    assert balanced(); 

ネイティブメソッドがバランスツリーのデータ構造を実装している場合を除いて、通常は、各 public メソッドの先頭に同様のチェックを行う必要はありません。 この例では、各メソッド呼び出しの間に、メモリが破壊されるバグによってネイティブピアのデータ構造が破壊されることがあります。 メソッドの先頭でアサーションが失敗した場合は、メモリ破壊が発生したことを示します。 ただし、クラスの状態がほかのクラスによって変更される可能性がある場合は、できるだけメソッドの先頭でクラス不変条件をチェックしてください (クラスを設計するときは、クラスの状態がほかのクラスから直接参照できないようにすることをお勧めします)。

 

高度な使い方

以下のセクションで説明するトピックは、リソースに制約があるデバイスと、フィールド内のアサーションを無効にする必要があるシステムだけに当てはまります。 これらのトピックに関心がない場合は、次の「アサーションを使用するファイルのコンパイル」に進んでください。

 

すべてのアサーションのトレースをクラスファイルから削除する

リソースが制約されているデバイス向けのアプリケーションを開発している場合は、クラスファイルからアサーションをすべて削除することをお勧めします。 アサーションを削除すると、アサーションを有効にできなくなりますが、クラスファイルのサイズが小さくなり、多くの場合クラスをロードするときのパフォーマンスが向上します。 JIT の性能が高くない場合は、アサーションを削除することによってプログラムのサイズが小さくなり、実行時のパフォーマンスが向上します。

アサーション機能には、クラスファイルからアサーションを削除する機能はありません。 ただし、assert 文を使用するときに、 JLS 14.20 に規定されている「条件付きコンパイル」方式と組み合わせることができます。これにより、コンパイラが生成したクラスファイルから、すべてのアサーションのトレースを削除することができます。

    static final boolean asserts = ... ; // false to eliminate asserts

    if (asserts)
        assert <expr> ; 

 

アサーションを有効にするとき

重要なシステムを開発しているプログラマは、アサーションを無効にしたくないことがあります。 次の静的な初期化方式を使用すると、アサーションが無効になっている場合にクラスが初期化されません。

    static {
        boolean assertsEnabled = false;
        assert assertsEnabled = true; // Intentional side effect!!!
        if (!assertsEnabled)
            throw new RuntimeException("Asserts must be enabled!!!");
    } 

この静的初期化子は、クラスの先頭に追加します。

 


アサーションを使用するファイルのコンパイル

javac コンパイラがアサーションを含むコードを受け付けるようにするには、-source 1.4 コマンド行オプションを以下の例のように使用しなければなりません。

    javac -source 1.4 MyClass.java 
このフラグが必要なのは、ソースの互換性の問題が発生しないようにするためです。

 


アサーションの有効化および無効化

デフォルトでは、実行時にアサーションは無効になっています。 2 つのコマンド行スイッチを使用して、アサーションの有効/無効を切り替えることができます。

さまざまな詳細レベルでアサーションを有効にするには、-enableassertions スイッチまたは -ea スイッチを使用します。 さまざまな詳細レベルでアサーションを無効にするには、-disableassertions スイッチまたは -da スイッチを使用します。 詳細レベルは、次のようにスイッチに渡す引数を使用して指定します。

たとえば次のコマンドは、com.wombat.fruitbat パッケージとそのサブパッケージ内だけでアサーションを有効にして、BatTutor プログラムを実行します。

    java -ea:com.wombat.fruitbat... BatTutor

単一コマンド行にこれらのスイッチのインスタンスを複数指定した場合は、指定したスイッチが順番に処理されてからクラスがロードされます。 たとえば、次のコマンドは、com.wombat.fruitbat パッケージ内のアサーションを有効にし、com.wombat.fruitbat.Brickbat クラス内のアサーションを無効にして、BatTutor プログラムを実行します。

    java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat BatTutor 

上のスイッチはすべてのクラスローダに適用されます。 例外的に、明示的なクラスローダを持たないシステムクラスにも適用されます。 ただし、引数を取らないスイッチは、前述のようにシステムクラスには適用されません。 この動作を利用すれば、システムクラスを除くすべてのクラスでアサーションを簡単に有効にすることができます。また、通常はこのようにする必要があります。

すべてのシステムクラス内のアサーションを有効にするには、-enablesystemassertions または -esa という別のスイッチを使用します。 同様に、システムクラス内のアサーションを無効にするには、-disablesystemassertions または -dsa を使用します。

たとえば、次のコマンドは、システムクラス内に加えて、com.wombat.fruitbat パッケージとそのサブパッケージ内のアサーションを有効にして、BatTutor プログラムを実行します。

    java -esa -ea:com.wombat.fruitbat... 

クラスのアサーション状態 (有効または無効) は、クラスが初期化されるときに設定され、変更されません。 しかし、特に注意が必要な特殊なケースがあります。 一般的に望ましくはありませんが、メソッドやコンストラクタは、初期化の前に実行することができます。 このような実行は、クラス階層の静的な初期化に、循環定義が含まれる場合に発生します。

assert 文がそのクラスの初期化前に実行される場合、そのクラス内でアサーションが有効になっているように実行される必要があります。 このトピックについては、アサーションの仕様で詳しく説明します。


既存のプログラムとの互換性

Java プログラミング言語への assert キーワードの追加によって、既存のバイナリ (.class ファイル) に問題が発生することはありません。 ただし、assert を識別子として使用するアプリケーションをコンパイルすると、警告またはエラーメッセージが表示されます。 assert 識別子が許可される環境を許可されない環境に簡単に移行できるように、このリリースのコンパイラでは、次の 2 つの操作モードをサポートしています。

-source 1.4 フラグを使用して特にソースモード 1.4 を要求しない限り、コンパイラはソースモード 1.3 で動作します。 このフラグを指定することを忘れると、新しい assert 文を使用するプログラムはコンパイルされません。 コンパイラのデフォルトの動作として、assert を識別子として使用できる古いセマンティクスが使われているのは、ソースの互換性を最大限に維持するためです。 ソースモード 1.3 は、今後段階的にサポートされなくなる予定です。

 


設計に関する FAQ

ここでは、アサーション機能の設計に関する FAQ をまとめてあります。

 

一般的な質問

  1. なぜアサーション機能がサポートされたのでしょうか。Java プログラミング言語に対するアサーションのプログラミングは、特にサポートがなくても可能です。

    アサーション目的の実装も可能でしたが、各アサーションに if 文が必要になるためわかりにくくなったり、アサーションを無効にしても条件が評価されるため非効率になります。 また、アサーションの有効/無効を切り替えるときは、独自の方法で行うため、特にフィールド内でデバッグするときの汎用性が低くなります。 これらの短所があるため、Java プログラミング言語を使用するエンジニアの間でアサーションはそれほど普及していませんでした。 Java プラットフォームに対するアサーションのサポートは、このような状況を改善することを目的としています。

     

  2. なぜアサーションの実装に、ライブラリの変更ではなく、言語の変更を採用したのですか。

    言語の変更は、多くの工数を必要とし、簡単に行うことはできません。 ライブラリを使用した方法も考慮しました。 しかし、アサーションを無効にした場合は、そのランタイムコストはごくわずかでなければならないと考えられています。 ライブラリを採用した場合、ランタイムコストを抑えるには、各アサーションを if 文としてハードコードしなければなりません。 多くのプログラマは、この方法は選択しないでしょう。 if 文を記述しないでパフォーマンスを犠牲にするか、アサーションをまったく行わないかでしょう。 実は、James Goslin が最初に開発した Java プログラミング言語の仕様には、アサーションが組み込まれていました。 しかし、時間の制約により満足のいく設計と実装を行うことができなかったため、Oak 仕様からは削除されました。

     

  3. 事前条件、事後条件、およびクラスの不変条件に関して、Eiffel プログラミング言語のような契約による設計を本格的に適用しなかったのはなぜですか。

    契約による設計を適用することも考慮しました。しかし、この方法を Java プログラミング言語に適用するには、Java プラットフォームライブラリを大幅に変更する必要があり、古いライブラリとの間に大きな不整合が発生する可能性がありました。 また、契約による設計を採用したときに、Java プログラミング言語の特性である単純さを維持できることを確信できませんでした。 あらゆることを考慮した結果、単純な boolean 型のアサーション機能の方が、はるかに単純であり、大幅にリスクが少ないという結論に達しました。 ただし、boolean 型アサーション機能を Java に追加しても、将来のある時点で、契約による設計を本格的に適用する可能性はあります。

    単純なアサーション機能を使用した場合でも、契約による設計方式のプログラミングを、限定的に導入することができます。 assert 文は、public でない事前条件、事後条件、およびクラス不変条件のチェックに適しています。 public な事前条件をチェックするときは、メソッド内で行う必要があります。この場合、IllegalArgumentExceptionIllegalStateException など、ドキュメント化された例外が発生します。

     

  4. アサーションを無効にしたときにコードブロック全体の実行を抑制する機能など、boolean 型のアサーション以外に assert に似た構文が提供されないのはなぜでしょうか。

    このような構文を提供すると、アサーションを別のメソッドに分離させた方がよい場合にも、プログラマが複雑なアサーションをインラインに配置する可能性があるためです。

 

互換性

  1. 新しいキーワードを導入すると、assert が識別子として使用されている既存のプログラムとの間に互換性の問題が発生しませんか。

    ソースファイルについては、互換性の問題が発生します。 ただし、assert を識別子として使用するクラスのバイナリは、引き続き正常に機能します。 ソースファイルを簡単に移行するには、「ソースの互換性」を参照してください。移行期間中でも、開発者は引き続き assert を識別子として使用できます。

     

  2. この機能のために、古い JRE に対して実行できないクラスファイルが生成されることはありませんか。

    あります。 クラスファイルは、desiredAssertionStatus などの新しい ClassLoader メソッドと Class メソッドの呼び出しを含みます。 これらのメソッドの呼び出しを含むクラスファイルが、ClassLoader クラスにこれらのメソッドが定義されていない古い JRE に対して実行されると、プログラムの実行が失敗し、NoSuchMethodError がスローされます。 一般的に、新しい機能を使用するプログラムと古いリリースとの互換性を維持できないのは、このような場合です。

 

構文およびセマンティクス

  1. Expression 2 内でプリミティブ型を使用できるのはなぜですか。

    この式の型は、特に制限する必要はありません。 任意の型を使用できれば、開発が容易になります。たとえば、各アサーションに対して一意の整数コードを関連付けるときに使用できます。 任意の型に対応するため、この式は、System.out.println(...) の引数のようになっています。

 

AssertionError クラス

  1. Expression2 を指定しないで assert 文を実行したときに、AssertionError が生成された場合、表明した条件のプログラムテキストが詳細メッセージ (たとえば "height < maxHeight") として使用されないのはなぜですか。

    このようにすると、アサーションが使いやすくなる場合があります。しかし、この利点を考慮しても、すべての文字列定数を .class ファイルと実行時イメージに追加するコストは無視できません。

     

  2. AssertionError が発生したときに、エラーを生成したオブジェクトにアクセスできないのはなぜですか。 同様に、詳細メッセージの代わりに、アサーションから任意のオブジェクトを AssertionError コンストラクタに渡さないのはなぜですか。

    これらのオブジェクトにアクセスできるようにすると、プログラマがアサーションの失敗からの回復を試みる可能性があります。これは、AssertionError の目的から逸脱します。

     

  3. AssertionError が発生したときに、getFilegetlinegetMethod のようなコンテキストアクセス用メソッドを使用できないのはなぜですか。

    AssertionError は、Throwable オブジェクトにもっとも適しています。このため、アサーションエラー以外に、あらゆるスロー可能なオブジェクトに使用できます。 また、この機能を提供するために、getStackTrace を使用して Throwable オブジェクトを拡張しました。

     

  4. AssertionError は、なぜ RuntimeException ではなく Error のサブクラスなのですか。

    この問題には、さまざまな意見がありました。 技術者グループは、この問題について徹底的に議論しました。その結果、Error のサブクラスにすれば、プログラマがアサーションの失敗から回復しようとする可能性が低くなる、という結論に達しました。 一般的に、アサーションの失敗の原因を特定するのは、困難または不可能です。 アサーションの失敗は、プログラムが予期しない動作を実行していることを示してします。このため、実行し続けると、障害が発生する可能性があります。 また、メソッドに指定されている実行時例外は、スローされる頻度の高いもので、ドキュメントには @throws コメントを使って記述します。 メソッドの仕様に対して、アサーションの失敗を生成するロジックを記述しても、ほとんど意味がありません。 アサーションの失敗は、実装の詳細項目と見なされており、実装およびリリースごとに異なります。

 

アサーションの有効化および無効化

  1. オブジェクトファイルからアサーションを完全に削除するコンパイラフラグは、なぜ提供されないのですか。

    フィールド内でアサーションを有効にする機能は、常に要求されており、あれば使いやすさが向上します。 コンパイル時にオブジェクトファイルからアサーションを削除する機能も、実装することはできます。 アサーションには目的以外の処理が含まれていることがあるため (できるだけ使用しないでください)、フラグを使用すると、プログラムの動作が大幅に変更されることがあります。 有効な Java プログラムに関連付けるセマンティクスは、できれば 1 つだけにしてください。 また、アサーションをオブジェクトファイルに残しておき、フィールド内で有効/無効を切り替えられるようにすることをお勧めします。 また、アサーションの仕様により、クラスが初期化前に実行される場合、アサーションは有効になっているように動作する必要があります。 アサーションがクラスファイルから削除されると、これらのセマンティクスを提供できなくなります。 ただし、JLS 14,20 に記述されている標準の「条件付きコンパイル」を使用すれば、必要に応じてこの効果を実現できます。

     

  2. アサーションを有効または無効にするコマンドが、従来のパッケージ型のセマンティクスではなく、パッケージツリー型のセマンティクスを使用するのはなぜですか。

    セマンティクスを階層構造で制御できれば、プログラマにとって便利です。コードを体系化するときに、パッケージ階層を使用することが多いためです。 たとえば、パッケージツリー型のセマンティクスを使用すれば、すべての Swing 内のアサーションを一度に有効または無効にすることができます。

     

  3. setClassAssertionStatus を呼び出したタイミングが遅かったために、アサーション状態を設定できなかった場合 (指名したクラスがすでに初期化されている)、例外がスローされずに、boolean 値が返されるのはなぜですか。

    タイミングの問題でアサーション状態を設定できない場合、警告メッセージなど以外は、対処は必要ないか、対処しないでください。 例外のスローは適切ではありません。

     

  4. setDefaultAssertionStatus および setAssertionStatus の代わりに、単一メソッド名をオーバーロードしないのはなぜですか。

    メソッドの名前付けでは、わかりやすさを優先します。 オーバーロードは、混乱を招く傾向があります。

     

  5. desiredAssertionStatus のセマンティクスを調整して、クラスがすでに初期化されている場合に実際のアサーションステータスを返すようにすると、プログラマにとって使いやすくなるはずですが、そのようにしないのはなぜですか。

    そのように調整したメソッドの用途が明白ではありません。 このメソッドは、アプリケーションプログラマによる使用を想定して設計されていません。また、処理が遅くなり必要以上に複雑になることは望ましくありません。

     

  6. アプレットからアサーションを有効化または無効化できないようにする RuntimePermission がないのはなぜですか。

    アプレットからは、ClassLoader メソッドを呼び出して、アサーション状態を変更することはありません。また、変更しても効果はありません。 初期化するクラスのアサーションを有効にすると、アプレットのパフォーマンスが低下することがあります。 アプレットからアクセス可能なクラスローダを使用してクラスをロードした場合は、そのクラスのアサーション状態はアプレットから変更できます。 既存の RuntimePermission は、信頼できないコードがクラスローダ (getClassLoader) にアクセスするのを防止します。

     

  7. 包含クラスのアサーション状態を照会する構文がないのはなぜですか。

    このような構文を実装すると、複雑なアサーションコードがインラインで記述される可能性がある、と判断したためです。 包含クラスのアサーション状態を照会する場合は、現在の API の最上位でアサーション状態を照会してください。

       boolean assertsEnabled = false;
       assert assertsEnabled = true;  // Intentional side-effect!!!
       // Now assertsEnabled is set to the correct value 
  8. assert 文がそのクラスの初期化前に実行されると、そのクラス内でアサーションが有効にされたように動作するのはなぜですか。

  9. クラスのコンストラクタとメソッドをクラスの初期化前に実行できることを知っているプログラマは多くありません。 クラスの初期化前には、クラスの不変条件がまだ確立されていない可能性が高く、そのために見つかりにくい重大なバグが発生する可能性があります。 この状態で実行されるアサーションはすべて失敗する可能性があり、プログラマにとって問題となります。 したがって、一般的に、この状態のときに検出されたアサーションをすべて実行するのがプログラマにとって有効です。


Sun
Java ソフトウェア

Copyright © 2002 Sun Microsystems, Inc. All Rights Reserved.