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 は、今後段階的にサポートされなくなる予定です。

Java コレクション API の設計に関する FAQ

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

一般的な質問

互換性

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

AssertionError クラス

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


Sun
Java ソフトウェア

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