![]() |
Assertion 機能 |
Documentation Contents |
性能を上げるために assertion チェックを無効化できます。プ ログラムの開発とテスト中は assertion チェックを有効にして おき、実際に使用する段階では無効化することが一般的です。
assertion は無効化されることがあるので、プログラムは assertion 中の真偽式が評価されることを仮定してはいけません。 そのため、これらの式は副作用を持つべきではありません。換言 すれば、こういった式は、評価が完了した後で観測され得る状態 に影響を与えるべきではありません。ある assertion 中の真偽 式が副作用を持つことは不正なことではありませんが、一般によ いことではありません。assertion の真偽式に副作用があると、 assertion が有効か無効かによってプログラムの振る舞いが変わっ てしまうことがあるからです。
同様に、public メソッドの引数チェックに assertion を使うべ きではありません。引数チェックは一般にはメソッドの責任であ り、この責任は assertion の有効無効に関わらず遂行されなけ ればなりません。引数チェックに assertion を使うことにはさ らに別の問題もあります。誤った引数は IllegalArgumentException、IndexOutOfBoundsException、 NullPointerException といった実行時例外を引き起こすべきで す。しかし、assertion の失敗は例外を throw しません。
より詳しく知るには、以下のリンクを辿って下さい:
javac -source 1.4 MyClass.java
assert
キーワードのために文法規則がひとつ変更
され、ひとつ追加されました:
StatementWithoutTrailingSubstatement:assert 文のどちらの書式でも Expression1 は boolean 型でなければ ならず、そうでない場合はコンパイル時エラーが起きます。<All current possibilities, as per JLS, Section 14.4 > AssertStatementAssertStatement:assert Expression1;
assert Expression1 : Expression2 ;
AssertionError
が throw されます。もし assertion が (コロンに続けて) 2つ
目の式を持つ場合、この式は評価されて AssertionError のコン
ストラクタに渡されます。2つ目の式がなければ、パラメータを
とらないコントラクタが使われます。(もしひとつ目の式が真な
ら、ふたつ目の式は評価されません。)
もしどちらかの式の評価中に例外が throw された場合、 assert 文はそこで完了し、この例外を throw します。
次のスイッチは、様々な粒度で assertion を有効にします。
java [ -enableassertions | -ea ] [:<パッケージ名>"..." | :<クラス名> ]引数がなければ、このスイッチによって assertion がデフォル トで有効になります。 引数がひとつ与えられた場合、引数が
"..."
で終
わっていればパッケージが指定されたとみなされ、assertion は
そのパッケージとサブパッケージ内で有効になります。引数が単
に "..." ならば、カレントディレクトリ中の名前なしパッケー
ジ内で有効になります。引数が "..."
同様に、次のスイッチは assertion を無効化します。
java [ -disableassertions | -da ] [:<パッケージ名>"..." | :<クラス名> ]これらのスイッチが複数組指定された場合、それらはクラスのロー ド時に順に処理されます。例えば、
com.wonbat.fruitbat
パッケージ (とそのサブパッ
ケージ) 中でだけ assertion を有効にしたい場合、次のコマン
ドが使えます:
java -ea:com.wombat.fruitbat... <Main Class>
com.wombat.fruitbat
パッケージでは有効にして
com.wombat.fruitbat.Brickbat
クラスでは無効
としたい場合、次のコマンドになります:
java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat <Main Class>上記のスイッチは、すべてのクラスローダと (クラスローダを持 たない) システムクラスに適用されます。この規則にはひとつ例 外があります: 引数が与えられない場合、これらのスイッチはシ ステムクラスには適用されません。これによって、シス テムクラス以外の全クラスで簡単に assert をオンにできます。 全システムクラスで assert を有効にするためには、別のスイッ チが用意されています。
java [ -enablesystemassertions | -esa ]これと対称に、全システムクラスで assert を無効化するために 次のスイッチが用意されています。
java [ -disablesystemassertions | -dsa ]
public void setDefaultAssertionStatus(boolean enabled);あるクラスがロードされるとき、そのクラスのパッケージ名かク ラス名の assertion 状態についてクラスローダに対して (下記 の新メソッドを用いて) 指定されたことがあるなら、そういった 指示はクラスローダのデフォルトの assertion 状態よりも優先 されます。さもなければ、そのクラスの assertion 状態はクラ スローダのデフォルトの assertion 状態に従います。
public void setPackageAssertionStatus(String packageName, boolean enabled);
public void setClassAssertionStatus(String className, boolean enabled);
public void clearAssertionStatus();
ここでは assert 構文の適当な使用例と不適当な利用例を挙げま す。これらの例は徹底したものではありません。assert 構文の 意図された使用法を伝えることが目的です。
assertion 機能を使えない状況では、多くのプログラマは以下の 方法でコメントを使うでしょう:
if (i%3 == 0) { ... } else if (i%3 == 1) { ... } else { // (i%3 == 2) ... }不変条件を記述するには、コメントを assert に変更すべきです。 (assert が多分岐 if 文中の
else
節を守るよう
に) 上記の例を変更します:
if (i % 3 == 0) { ... } else if (i%3 == 1) { ... } else { assert i%3 == 2; ... }% オペレータは本来の mod オペレータではなく剰余を計算する ので値が負となることがあります。そのため、
i
が負の場合上記の assertion は失敗し得ることに注意して下さ
い。
例えば:
switch(suit) { case Suit.CLUBS: ... break; case Suit.DIAMONDS: ... break; case Suit.HEARTS: ... break; case Suit.SPADES: ... }プログラマはおそらく、4つの case のうちひとつは常に実行さ れるということを当然に思うことでしょう。この仮定を試験する には、以下の default ケースを加えます:
default: assert false;より一般的には、実行が達しないとプログラマが仮定する箇所に 以下の文を記述します。
assert false;例えばこういったメソッドがあったと考えてください:
void foo() { for (...) { if (...) return; } // 実行はここに達するべきではありません!!! }最後のコメントを次の文で置き換えます:
assert false;このテクニックは注意深く使ってください。もしある文が実行さ れない (と JLS 14.19 で定義されている) なら、assert を記述 することでコンパイル時エラーに遭うでしょう。
/** * リフレッシュレートを設定します。 * * @param rate リフレッシュレート, 単位は frames per second。 * @throws IllegalArgumentException rate <= 0 か * rate > MAX_REFRESH_RATE の場合。 */ public void setRefreshRate(int rate) { // public メソッドの事前条件を守らせます if (rate <= 0 || rate > MAX_REFRESH_RATE) throw new IllegalArgumentException("Illegal rate: " + rate); setRefreshInterval(1000/rate); }assert 構文が追加されたからといってこの慣習は影響を受けま せん。assert はこういった事前条件には不適当です。チェック がメソッドに埋め込まれていれば、assertion が有効か無効かに 関わらず、引数チェックは確実に行われます。さらにまた、 assert 構文は指定された型の例外を throw しません。
しかしもし、public ではないメソッドに事前条件があり、クラ スの使用者がそのクラスを用いて行うことに対して事前条件は何 も影響しないとクラスの作者が信じるなら、assertion は適当で す。例えば:
/** * リフレッシュ間隔を設定します (正当なフレームレートに対応しなければなりません)。 * * @param interval ミリ秒で表したリフレッシュ間隔。 */ private void setRefreshInterval(int interval) { // public ではないメソッドで事前条件が守られていることを確認します assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE; ... // リフレッシュ間隔の設定 }MAX_REFRESH_RATE が 1000 より大きくて利用者が 1000 より大 きいリフレッシュレートを選んだ場合に、上記の assertion は 失敗するでしょう。これは実際は、ライブラリにバグがあること のあらわれです。
/** * 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 文と単純な内部クラスの助けによって可能となり ます。内部クラスはひとつ以上の変数の状態を保存して、処理の 後でチェックできるようにします。例えば、次のようなコード片 があったとします:
void foo(int[] array) { // 配列の操作 ... // この時点で、配列は操作前とまったく同じ順序を保っている }このように変更することで、上のメソッドでは文章として書かれ ているだけの assertion を機能させることができます。
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; // 常に成功します; 配列のコピーを保存するという副作用を持ちます assert (copy = new DataCopy()) != null; ... // 配列の操作 assert copy.isConsistent(); }この慣用句は、ひとつ以上のデータフィールドを保存できるよう に、また、処理前後の値に関する任意の複雑な assertion を試 験できるように、容易に一般化できます。
ひとつ目の assert 文 (これは副作用のためだけに実行されます ) は次のより明示的な記述に置き換えることができます。
copy = new DataCopy();しかしこれは、assert が有効かどうかに関わらず配列をコピー するので、無効化されたときには何のコストもかからないという assert の利点を損なっています。
// このツリーが適当に釣り合っていれば true を返す private boolean balanced() { ... }このメソッドはクラス中で不変の条件をチェックします。クラス 中のすべてのメソッドが完了する前後で、常に真となります。こ れを確認するためには、すべての public メソッドとコンストラ クタに対して return の直前に次の行を記述します:
assert balanced();同様のチェックを public メソッドの先頭にも記述することは一 般にやり過ぎです。ただ、データ構造がネイティブメソッドで実 装されている場合は、メソッド呼び出しの間に "ネイティブピア (peer)" がバグによって壊されてしまうことがあります。メソッ ド先頭での assertion 失敗は、こういったメモリ破壊が起きて しまったことを示します。同様に、状態を他のクラスから変更で きてしまうようなクラスでは、メソッド先頭で不変条件チェック を行うことが得策かもしれません。(より良い方法は、他のクラ スから状態が直接見えてしまうことのないようにクラスを設計す ることです。)
assertion 機能は、クラスファイルからの assertion の除去を 直接はサポートしていません。しかし、assert 文を "条件コン パイル" 慣用句 (JLS 14.19) と共に使うことは可能です:
static final boolean asserts = ... ; // false なら assert を除去します if (asserts) assert <expr> ;assert をこのように使うと、コンパイラがこれらの assert の すべての痕跡をクラスファイルから取り除くことが可能になりま す。資源の量が厳しいデバイス向けのコード生成を支援する目的 では、このような除去が推奨されます。
static { boolean assertsEnabled = false; assert assertsEnabled = true; // 意図的な副作用!!! if (!assertsEnabled) throw new RuntimeException("Asserts must be enabled!!!"); }
-source 1.4このフラグが指定されない場合、ソースコードの互換性が尊重さ れ、振る舞いは "1.3" のものとなります。1.3 互換ソースとの 互換性サポートは、時間とともに廃止されていくでしょう。
場あたり的な実装は可能ですが、それらは必然的に見苦しく
(assertion それぞれに if
文が要ります)、非効
率的 (assertion が無効であっても条件は評価される) なものと
なります。加えて、場あたり的実装は assertion の有効化、無
効化のためにそれぞれ固有の方法を持っていて、そのことが特に
現場でのデバッグ時の有用性を損ねています。こういった欠点が
あったため、これまで assertion が Java 文化の一部となるこ
とはありませんでした。Java プラットフォームに assertion サ
ポートを追加することで、この状況を改善できる見込みがありま
す。
我々は、言語への変更は重大な取り組みであって、軽々しく着手
されるべきではないことを知っています。ライブラリを用いたア
プローチも検討しました。しかし、無効化すればassertion の実
行時コストは無視できるということが必須だと思われました。こ
れをライブラリで達成するには、それぞれの assertion を
if
文としてハードコードすることをプログラマに
強制せねばなりません。多くのプログラマはそうはしないでしょ
う。プログラマは if 文を省略して性能が犠牲となるか、その機
能は完全に無視されるかのどちらかだったでしょう。
assertion はジェームスゴスリン氏による最初の Java の仕様に
含まれていたことを付記します。満足な設計と実装をするだけの
充分な時間がなかったために、assertion は Oak の仕様から取
り除かれました。
そういった機能を提供することを検討しました。しかし、それを Java 言語に融合させるためには Java プラットフォームライブ ラリへの多大な変更を要し、また、新旧ライブラリ間の不整合も 大きくなりそうでした。大きな変更、不整合なしにできるとは思 えませんでした。さらにまた、そういった機能を追加してなお、 Java 言語の十八番であるシンプルさが保たれるという確信も持 てませんでした。すべてを考慮した結果、我々は単純な boolean assertion 機能がとても素直でリスクの小さい解決法だ という結論に至りました。boolean assertion 機能を言語に追加 したからといって、将来、契約による設計のひと通りの機能を加 えることができなくなったわけではないことを述べておきます。
単純な assertion 機能は契約による設計スタイルのプログラミ ングを、限定された形で可能にします。assert 文は事後条件と クラス不変条件のチェックに適します。事前条件チェックは相変 わらずメソッド内で行うべきです。事前条件チェックは、API 仕 様書に書かれた特定の例外を throw します。 IllegalArgumentException や IllegalStateException といった 例外です。
そういった構文を用意すると、プログラマは、本来は別メソッ ドに分離すべき複雑な assertion を構文中に書いてしまいがち です。
ソースファイルでは問題となります。(assert を識別子とし て使うバイナリはきちんと動作し続けます。) 移行を容易にする ために、移行期間中は assert を識別子として使い続けられるよ うな方策を述べました。
強いてこの式の型を制限する理由はありません。任意の型を 許すことで、例えばそれぞれの assertion に固有の整数値を結 びつけたいといったプログラマに便宜を与えられます。また、こ れによって、System.out.print(...) といった式を書けます。望 ましいことです。
そうすることで、ある場合には assertion の利用者側での利 便性が向上するかもしれません。しかし、その恩恵があろうと、 すべてのそれらの文字列定数を .class ファイルと実行時イメー ジに加えてしまうコストを正当化する理由にはなりません。
それらのオブジェクトにアクセスできると、プログラマは assertion 失敗からの復帰を試みてしまうでしょう。それはこの 機能の目的に沿わない誤用です。
この機能は Throwable によって最もうまく提供されていて、 assertion エラーからだけでなく、すべての例外、エラーから使 うことができます。我々はこの機能を提供するように assertion 機能の最初の導入と同じリリースで Throwable を改 良するという意図を持っていました。(訳注: Throwable#getCause, initCause が導入された。)
この問題は物議をかもしました。エキスパートグループは詳 細に議論し、次の結論に達しました。プログラマが assertion 失敗からの復帰を試みないようにするにはError の方が適当であ る、と。assertion 失敗の原因を追求することは一般には困難で あるかまたは不可能です。こういった失敗は、プログラムが "既 知の空間の外側 (outside of known space)" に作用していると いうことを示し、実行の再開を試みることはおそらく有害です。 また、メソッドは throw し得る RuntimeException の多くを ("@throws" doc コメントを通じて) 明記するという取り決めに なっています。メソッドの仕様に assertion が失敗し得る状況 を記述するというのはほとんど意味がありません。そういった情 報は仕様ではなく実装の細部に属し、実装から実装、リリースか らリリースに従って変わり得るものだとみなされています。
実用性を高めるために、現場で assertion を有効にできるこ とには確固とした要求があります。それと同時に、開発者がコン パイル時にオブジェクトファイルから assertion を取り除く ことも可能でした。しかし assertion は (そうであるべきでは ないにも関わらず) 副作用を持つことができるため、そういった フラグがプログラムの振る舞いを著しく変えてしまうことがあり ます。正しい Java プログラムに対してはただひとつのセマンティ クスがあるべきだと考えられています。また、現場で assert を 有効にできるように assert をオブジェクトファイル中に残すこ とを我々は推奨したいのです。最後に、JLS 14.19 に記述されて いる "条件コンパイルのための慣用句 (conditional compilation idiom)" を利用することで、本当に望む開発者は完 全な除去を行えます。
プログラマが本当にコードを系統立てるためにパッケージ階 層を用いるなら、階層的な制御が有用となります。例えば、 Swing のすべてに対して assertion を有効化、無効化すること が可能です。
assertion 状態の設定が遅すぎた場合には何もしないことが 必要であるか望ましいです。例外は重すぎます。
メソッドの名付け方を明快にする方がよいからです。
アプレットが assertion 状態を変えるために ClassLoader のメソッドを呼ぶ理由はないのですが、それを許すことに害はな いように思われます。最悪でも、アプレットは後にロードされる べきクラスの assert を有効にすることで弱いサービス不能 (denial-of-service) 攻撃ができるくらいです。また、アプレッ トはアプレットがアクセスできるクラスローダによってロードさ れるクラスに対してしか assert 状態を変えることはできません。 不審なコードがクラスローダへのアクセスを得てしまう ことを 防ぐ RuntimePermission はすでに存在します (getClassLoader)。
そういった構文があると、人は複雑な assertion コードを書 きがちです。悪い例を挙げます:
if (assertsEnabled()) { ... }また、現在の API を使って assert 状態を問い合わせることも 簡単なことです。もし必要だと思うなら:
boolean assertsEnabled = false; assert assertsEnabled = true; // 意図的な副作用!!! // ここで assertsEnabled は正しい値に設定されます
Copyright © 2002 Sun Microsystems, Inc. All Rights Reserved. |
![]() Java Software |