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