Java 2 SDK 開発ガイド (Solaris 編)

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

assert 構文は、「規約による設計」を完全に適用していない機能ですが、プログラミングにおける非公式の「規約による設計」スタイルをサポートするのに役立ちます。

事前条件

規約上、公開メソッドの事前条件はメソッド内の明示的なチェックによって実施され、結果として、指定された特別な例外が発生します。次に例を示します。


 /**
     * 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 構文を追加しても、この規約には影響ありません。アサーションが有効であるかどうかにかかわらず、アサーションが入っているメソッドが必ず引数チェックを実施するような事前条件に対しては、アサーションは不適切です。さらに、assert 構文は指定されたタイプの例外をスローしません。

しかし、private メソッドには事前条件があり、かつ、クラスの作成者が「クライアントがそのクラスで何をしてもその事前条件は保たれる」と考えている場合には、アサーションは適切です。次に例を示します。


  /**
    * Sets the refresh interval (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;

        ... // Set the refresh interval
    }

MAX_REFRESH_RATE1000 よりも大きく、ユーザが選択したリフレッシュレートが 1000 よりも大きい場合、上記アサーションは失敗するので注意してください。 つまり、これはライブラリのバグを示しています。

事後条件

public メソッドに指定されているかどうかにかかわらず、事後条件チェックはアサーションで実装するのが最適です。次に例を示します。


 /**
     * 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);
        if (!this.gcd(m).equals(ONE))
          throw new ArithmeticException(this + " not invertible mod " + m);

        ... // Do the computation

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

実際には、2 番目の事前条件(this.gcd(m).equals(ONE)) は冗長であるので、計算を実行する前にチェックしません。この事前条件のチェックは、標準アルゴリズムによるモジュラ乗法逆数計算の副作用として行われます。

場合によっては、計算を実行する前にいくつかのデータを保存しておき、計算が完了した後に事後条件をチェックする必要があります。このような事後条件のチェックを行うには、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) {

        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

        assert copy.isConsistent();
     }

このメソッドを簡単に説明すると、複数のデータフィールドを保存して、計算前後の値に関連する複雑なアサーションを任意にテストします。

最初の assert 文 (副作用として単独で実行される) をよりわかりやすくすると、次のようになります。


  copy = new DataCopy();

しかし、アサーションが有効であるかどうかにかかわらず、この文は配列をコピーするので、「無効である場合、アサーションは何にも影響してはならない」という規則に違反します。

クラスの不変条件

すでに述べたとおり、アサーションは内部不変条件をチェックするのに適切です。アサーション機構自体は表明を行うために特別なスタイルを要求しません。ときには、必要な制約をチェックする数多くの式を単一の内部メソッドに結合して、そのメソッドをアサーションで呼び出すようにする方が便利な場合もあります。たとえば、何かのバランスツリーのデータ構造を実装しようとしていると仮定します。この場合はおそらく、ツリーが実際に (データ構造が示すとおりに) 効率的に構築されているかどうかをチェックする private メソッドを実装する方が適切です。


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

このメソッドはクラス不変条件です。どのメソッドにおいても、クラス不変条件は常に (メソッドが完了する前でも後でも) 真である必要があります。これをチェックするには、次のようにアサーションでチェックします。


assert balanced();

各 public メソッドとコンストラクタの直前に、assert 行を置きます。データ構造がネイティブメソッドによって実装されている場合を除いて、一般的に、各 public メソッドの先頭に同様なチェックを置く必要はありません。この場合、メソッドの呼び出し間に、メモリー破壊のバグが「ネイティブピア」のデータ構造を破壊する可能性があります。このようなメソッドの先頭にあるアサーションが失敗した場合、このようなメモリー破壊が発生したことを意味します。同様に、ほかのクラスによって状態が変更される可能性があるクラスにおいては、クラス不変条件のチェックをメソッドの先頭に置くことが望まれます。しかし、クラスの状態はほかのクラスから直接見ることができないように設計することが推奨されています。