The King's Museum

ソフトウェアエンジニアのブログ。

【Effective Java】項目57:例外的状態にだけ例外を使用する

例外は例外的な状況に対してのみ利用するべきです。 正常なパスで例外を利用するべきではありません。

よいパフォーマンスを得ようとして、次のようなコードを書く人達がいます。

try {
    int i = 0;
    while (true) 
        range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

このコードは「range 配列をイテレートする」という機能を持っています。 ただし、このコードを見ても何を意図しているのかよく分かりませんし、明らかに異常なコードです。

このコードは、次の理由によりパフォーマンス上の利点も得られません。

  • 例外は通常は使用されないことが想定されています。そのため、例外は多くの JVM 実装でコードの実行を遅くします。
  • try-catch ブロック内にコードを書くと、JVM 実装の最適化が排除されます
  • 配列をループするために標準イディオムを使えば、配列の境界チェックは最適化されます

実際に測定すると、例外に基づいたイディオムは通常のイテレートイディオムと比較して2倍程度遅いです。

例外と API 設計

よい API は正常なパスにおいて例外を使用することをクライアントに強制しません。

ある状態でしか呼び出してはいけないメソッドを持つクラスでは、呼び出してよいかどうかをチェックするメソッドを持っています。 例えば Iterator クラスでは next() を呼び出してよいかをチェックする hasNext() を持っています。

一方、チェックメソッドを持たず、呼び出しが成功したかどうかを区別する戻り値を返すというメソッドがあります。 不適切な状態でメソッドが呼ばれた場合には null などの値が返ります。

オブジェクトが外部からの同期なしで並行アクセスされる場合、この「戻り値方式」を使う必要があります。 なぜなら、対象のメソッドとチェックメソッドの間で状態が変更されるかもしれないからです。

しかし、一般的にはチェックメソッド方式の方がよいとされています。 その方が可読性が高いですし、チェックメソッドを呼び忘れた場合には例外が発生すらため、バグが明らかになりやすいからです。

(c) The King's Museum