アサーションを使用したプログラミング |
ドキュメントの目次 |
アサーションは、プログラムに関する前提をテストできる Java TM プログラミング言語の文です。たとえば、粒子の速度を計算するメソッドを記述した場合に、計算される速度が光速よりも遅いことを前提とすることがあります。
各アサーションは、アサーションが実行されたときに true になると想定される boolean 式を含んでいます。true にならない場合は、システムによってエラーがスローされます。アサーションは、boolean 式が true であることを確認することによって、プログラムの動作に関する前提を検証します。これによって、プログラムにエラーがない可能性が高くなります。
プログラミング中にアサーションを記述すると、すばやくかつもっとも効果的にバグを発見して修正できることが経験的に実証されています。さらに、アサーションはプログラムの内部的な動作の文書化に役立つので、保守が容易になるという利点もあります。
このドキュメントでは、アサーションを使ったプログラミング方法について説明します。次のトピックについて説明します。
アサーション文は 2 つの形式で記述できます。最初に単純な形式を示します。
assert Expression1 ;
Expression1 は、boolean
式です。アサーションは、システムによって実行されると、Expression1 を評価し、結果が false
の場合は、詳細メッセージを表示しないで AssertionError
をスローします。
次に、アサーション文の 2 つ目の形式を示します。
assert Expression1 : Expression2 ;
説明
void
として宣言されたメソッドの呼び出しをこの式として使うことはできません。
この形式の assert
文は、AssertionError
の詳細メッセージを提供するために使用します。システムが Expression2 の値を適切な AssertionError
コンストラクタに渡し、コンストラクタは値の文字列表現をエラーの詳細メッセージとして使用します。
詳細メッセージの目的は、アサーションの失敗の詳細を把握して伝達することです。このメッセージを参照して、アサーションの失敗の原因となったエラーを診断し、最終的にはエラーを解決できるようにする必要があります。詳細メッセージは、ユーザレベルのエラーメッセージではないため、一般的に、そのままで理解できるメッセージにしたり、国際化したりする必要はありません。詳細メッセージは、失敗したアサーションを含むソースコードと組み合わせて、スタックトレース全体のコンテキスト内で解釈されます。
すべてのキャッチされない例外と同じように、一般的にスタックトレース内のアサーション失敗には、スロー元のファイルと行番号のラベルが付けられます。失敗の診断に役立つ追加情報をプログラムが提供できる場合にのみ、アサーション文の最初の形式よりも、2 番目の形式を優先的に使用します。たとえば、Expression1 に、2 つの変数 x
と y
の関係が含まれる場合は、2 番目の形式を使用する必要があります。このような状況では、Expression2 として "x: " + x + ", y: " + y
のような式がよく使われます。
場合によっては、Expression1 の評価に時間がかかることがあります。たとえばソートされていないリスト内の最小要素を検索するメソッドを記述し、選択された要素が確かに最小かどうかを確認するためのアサーションを追加するとします。アサーションによって実行される処理には、少なくともメソッド自体によって実行される処理と同じ時間がかかります。配備後のアプリケーションのパフォーマンスにアサーションが影響しないようにするために、プログラムの起動時にアサーションを有効または無効にすることができます。デフォルトではアサーションは無効になります。アサーションを無効にすると、パフォーマンスに対する影響が完全になくなります。無効になったアサーションは、セマンティクスおよびパフォーマンスの観点から見ると、基本的に空文と同じです。詳細については「アサーションの有効化および無効化」を参照してください。
Java プログラミング言語への assert
キーワードの追加によって既存のコードが影響を受けます。詳細については、「既存のプログラムとの互換性」を参照してください。
アサーションの使用が役に立つ状況は次のように数多くあります。
また、状況によっては、アサーションを使用しないようにする必要があります。
引数のチェックは通常、メソッドの仕様 (または規約) の一部になっており、アサーションの有効/無効にかかわらず、この仕様に準拠する必要があります。アサーションを使用して引数をチェックした場合、不正な引数によってランタイム例外 (IllegalArgumentException
、IndexOutOfBoundsException
、NullPointerException
など) が発生する可能性があります。アサーションが失敗しても、適切な例外はスローされません。
アサーションは無効になることがあるため、アサーションに含まれる boolean 式が評価されることを前提にしてはなりません。この規則を守らないと問題が発生します。たとえば、names
というリストからすべての null の要素を削除する必要があり、1 つ以上の null がリストに含まれていることがわかっているとします。これを次のように処理するのは間違いです。
// Broken!- action is contained in assertion assert names.remove(null);
アサーションが有効になっている場合はプログラムは正常に機能しますが、アサーションが無効になっていると、null 要素がリストから削除されないのでプログラムは正常に機能しません。処理を実行した後に、アサーションを実行して処理が正常に完了したことを表明するのが正しい方法です。
// Fixed - action precedes assertion boolean nullsRemoved = names.remove(null); assert nullsRemoved; // Runs whether or not asserts are enabled
一般的に、アサーション内では、副作用がない式を使用する必要があります。つまり、式の評価が完了した後の可視の状態に対して boolean 式が影響を与えてはなりません。ただしこの規則の例外として、アサーションは、ほかのアサーション内からのみ使用される状態を変更することはできます。この例外を利用するコードについては、このドキュメントで後述します。
アサーションが登場するまで、多くのプログラマは、コメントを使用してプログラムの動作に関する前提を示していました。たとえば、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 つの操作モードをサポートしています。
assert
を識別子として使用するプログラムを受け入れますが、警告が表示されます。このモードでは、プログラムでの assert
文の使用は許可されません。assert
を識別子として使用していると、コンパイラがエラーメッセージを生成します。このモードでは、プログラムでの assert
文の使用は許可されます。-source 1.4
フラグを使用して特にソースモード 1.4 を要求しないかぎり、コンパイラは、ソースモード 1.3 で動作します。このフラグを指定することを忘れると、新しい assert
文を使用するプログラムはコンパイルされません。コンパイラのデフォルトの動作として、assert
を識別子として使用できる古いセマンティクスが使われているのは、ソースの互換性を最大限に維持するためです。ソースモード 1.3 は、今後段階的にサポートされなくなる予定です。
ここでは、アサーション機能の設計に関する FAQ をまとめてあります。
アサーション目的の実装も可能でしたが、各アサーションに if
文が必要になるためわかりにくくなったり、アサーションを無効にしても条件が評価されるため非効率になります。また、アサーションの有効/無効を切り替えるときは、独自の方法で行うため、特にフィールド内でデバッグするときの汎用性が低くなります。これらの短所があるため、Java プログラミング言語を使用するエンジニアの間でアサーションはそれほど普及していませんでした。Java プラットフォームに対するアサーションのサポートは、このような状況を改善することを目的としています。
言語の変更は、多くの工数を必要とし、簡単に行うことはできません。ライブラリを使用した方法も考慮しました。しかし、アサーションを無効にした場合は、そのランタイムコストはごくわずかでなければならないと考えられています。ライブラリを採用した場合、ランタイムコストを抑えるには、各アサーションを if
文としてハードコードしなければなりません。多くのプログラマは、この方法は選択しないでしょう。if 文を記述しないでパフォーマンスを犠牲にするか、アサーションをまったく行わないかでしょう。実は、James Goslin が最初に開発した Java プログラミング言語の仕様には、アサーションが組み込まれていました。しかし、時間の制約により満足のいく設計と実装を行うことができなかったため、Oak 仕様からは削除されました。
契約による設計を適用することも考慮しました。しかし、この方法を Java プログラミング言語に適用するには、Java プラットフォームライブラリを大幅に変更する必要があり、古いライブラリとの間に大きな不整合が発生する可能性がありました。また、契約による設計を採用したときに、Java プログラミング言語の特性である単純さを維持できることを確信できませんでした。あらゆることを考慮した結果、単純な boolean 型のアサーション機能の方が、はるかに単純であり、大幅にリスクが少ないという結論に達しました。ただし、boolean 型アサーション機能を Java に追加しても、将来のある時点で、契約による設計を本格的に適用する可能性はあります。
単純なアサーション機能を使用した場合でも、契約による設計方式のプログラミングを、限定的に導入することができます。assert
文は、public でない事前条件、事後条件、およびクラス不変条件のチェックに適しています。public な事前条件をチェックするときは、メソッド内で行う必要があります。この場合、IllegalArgumentException
や IllegalStateException
など、ドキュメント化された例外が発生します。
このような構文を提供すると、アサーションを別のメソッドに分離させた方がよい場合にも、プログラマが複雑なアサーションをインラインに配置する可能性があるためです。
assert
が識別子として使用されている既存のプログラムとの間に互換性の問題が発生しませんか。
ソースファイルについては、互換性の問題が発生します。ただし、assert
を識別子として使用するクラスのバイナリは、引き続き正常に機能します。ソースファイルを簡単に移行するには、「ソースの互換性」を参照してください。移行期間中でも、開発者は引き続き assert
を識別子として使用できます。
できます。クラスファイルは、desiredAssertionStatus などの新しい ClassLoader メソッドと Class メソッドの呼び出しを含みます。これらのメソッドの呼び出しを含むクラスファイルが、ClassLoader クラスにこれらのメソッドが定義されていない古い JRE に対して実行されると、プログラムの実行が失敗し、NoSuchMethodError がスローされます。一般的に、新しい機能を使用するプログラムと古いリリースとの互換性を維持できないのは、このような場合です。
この式の型は、特に制限する必要はありません。任意の型を使用できれば、開発が容易になります。たとえば、各アサーションに対して一意の整数コードを関連付けるときに使用できます。任意の型に対応するため、この式は、System.out.println(...)
の引数のようになっています。
AssertionError
が生成された場合、表明した条件のプログラムテキストが詳細メッセージ (たとえば height < maxHeight
"このようにすると、アサーションが使いやすくなる場合があります。しかし、この利点を考慮しても、すべての文字列定数を .class
ファイルと実行時イメージに追加するコストは無視できません。
AssertionError
が発生したときに、エラーを生成したオブジェクトにアクセスできないのはなぜですか。同様に、詳細メッセージの代わりに、アサーションから任意のオブジェクトを AssertionError
コンストラクタに渡さないのはなぜですか。
これらのオブジェクトにアクセスできるようにすると、プログラマがアサーションの失敗からの回復を試みる可能性があります。これは、AssertionError の目的から逸脱します。
AssertionError
が発生したときに、getFile
、getline
、getMethod
のようなコンテキストアクセス用メソッドを使用できないのはなぜですか。
AssertionError は、Throwable
オブジェクトにもっとも適しています。このため、アサーションエラー以外に、あらゆるスロー可能なオブジェクトに使用できます。また、この機能を提供するために、getStackTrace
を使用して Throwable
オブジェクトを拡張しました。
AssertionError
は、なぜ RuntimeException
ではなく Error
のサブクラスなのですか。
この問題には、さまざまな意見がありました。技術者グループは、この問題について徹底的に議論しました。その結果、Error
のサブクラスにすれば、プログラマがアサーションの失敗から回復しようとする可能性が低くなる、という結論に達しました。一般的に、アサーションの失敗の原因を特定するのは、困難または不可能です。アサーションの失敗は、プログラムが予期しない動作を実行していることを示してします。このため、実行し続けると、障害が発生する可能性があります。また、メソッドに指定されている実行時例外は、スローされる頻度の高いもので、ドキュメントには@throws
コメントを使って記述します。メソッドの仕様に対して、アサーションの失敗を生成するロジックを記述しても、ほとんど意味がありません。アサーションの失敗は、実装の詳細項目と見なされており、実装およびリリースごとに異なります。
フィールド内でアサーションを有効にする機能は、常に要求されており、あれば使いやすさが向上します。コンパイル時にオブジェクトファイルからアサーションを削除する機能も、実装することはできます。アサーションには目的以外の処理が含まれていることがあるため (できるだけ使用しないでください)、フラグを使用すると、プログラムの動作が大幅に変更されることがあります。有効な Java プログラムに関連付けるセマンティクスは、できれば 1 つだけにしてください。また、アサーションをオブジェクトファイルに残しておき、フィールド内で有効/無効を切り替えられるようにすることをお勧めします。また、アサーションの仕様により、クラスが初期化前に実行される場合、アサーションは有効になっているように動作する必要があります。アサーションがクラスファイルから削除されると、これらのセマンティクスを提供できなくなります。ただし、JLS 14,20 に記述されている標準の「条件付きコンパイル」を使用すれば、必要に応じてこの効果を実現できます。
セマンティクスを階層構造で制御できれば、プログラマにとって便利です。コードを体系化するときに、パッケージ階層を使用することが多いためです。たとえば、パッケージツリー型のセマンティクスを使用すれば、すべての Swing 内のアサーションを一度に有効または無効にすることができます。
setClassAssertionStatus
を呼び出したタイミングが遅かったために、アサーション状態を設定できなかった場合 (指名したクラスがすでに初期化されている)、例外がスローされずに、boolean
値が返されるのはなぜですか。
タイミングの問題でアサーション状態を設定できない場合、警告メッセージなど以外は、対処は必要ないか、対処しないでください。例外のスローは適切ではありません。
setDefaultAssertionStatus
および setAssertionStatus
の代わりに、単一メソッド名をオーバーロードしないのはなぜですか。
メソッドの名前付けでは、わかりやすさを優先します。オーバーロードは、混乱を招く傾向があります。
そのように調整したメソッドの用途が明白ではありません。このメソッドは、アプリケーションプログラマによる使用を想定して設計されていません。また、処理が遅くなり必要以上に複雑になることは望ましくありません。
RuntimePermission
がないのはなぜですか。
アプレットからは、ClassLoader
メソッドを呼び出して、アサーション状態を変更することはありません。また、変更しても効果はありません。初期化するクラスのアサーションを有効にすると、アプレットのパフォーマンスが低下することがあります。アプレットからアクセス可能なクラスローダを使用してクラスをロードした場合は、そのクラスのアサーション状態はアプレットから変更できます。既存の RuntimePermission
は、信頼できないコードがクラスローダ (getClassLoader
) にアクセスするのを防止します。
このような構文を実装すると、複雑なアサーションコードがインラインで記述される可能性がある、と判断したためです。 包含クラスのアサーション状態を照会する場合は、現在の API の最上位でアサーション状態を照会してください。
boolean assertsEnabled = false; assert assertsEnabled = true; // Intentional side-effect!!! // Now assertsEnabled is set to the correct value
クラスのコンストラクタとメソッドをクラスの初期化前に実行できることを知っているプログラマは多くありません。クラスの初期化前には、クラスの不変条件がまだ確立されていない可能性が高く、そのために見つかりにくい重大なバグが発生する可能性があります。この状態で実行されるアサーションはすべて失敗する可能性があり、プログラマにとって問題となります。したがって、一般的に、この状態のときに検出されたアサーションをすべて実行するのがプログラマにとって有効です。
Copyright © 2002 Sun Microsystems, Inc.All Rights Reserved. |