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

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

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

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

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


はじめに

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

assert Expression1 ;

Expression1boolean式です。アサーションは、システムによって実行されると、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言語仕様に定義されているように文が到達不可能である場合は、到達しないことを表明しようとすると、コンパイル時にエラーが発生します。ここでも、単純に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メソッドのパラメータのチェックにはアサーションを使用しないでください。このメソッドは、常に引数チェックを適用することを保証するので、assertは適していません。これは、アサーションが有効かどうかにかかわらず引数をチェックする必要があります。さらに、assert構文は、指定した種類の例外をスローしません。assert構文がスローできるのは、AssertionErrorのみです。

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

/**
 * 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を超えるリフレッシュ・レートを選択すると、失敗します。つまり、ライブラリ内にバグが存在しています。

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

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

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文を使用するときに、Java言語仕様に規定されている「条件付きコンパイル」方式と組み合わせることができます。これにより、コンパイラが生成したクラス・ファイルから、すべてのアサーションのトレースを削除できます。

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

 if (asserts)
  assert <expr> ; 

アサーションを有効にすることを義務付ける

重要なシステムを開発しているプログラマは、現場でアサーションが無効にされないようにしたいことがあります。次のstaticイニシャライザを使用すると、アサーションが無効になっている場合にクラスが初期化されません。

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

このstaticイニシャライザは、クラスの先頭に追加します。

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

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 

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

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

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

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

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

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

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

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

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

設計に関するFAQ

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

一般的な質問

互換性

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

AssertionErrorクラス

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


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.