読者です 読者をやめる 読者になる 読者になる

【Effective Java】項目61:抽象概念に適した例外をスローする

例外翻訳

メソッドの内部で発生した下位レイヤの例外は、外側に伝播させないほうがいい場合があります。 下位レイヤで発生した例外をそのままスローすると、利用者が混乱したり API を実装の詳細で汚染する場合があります。

そのレイヤの概念と合わない例外が発生した場合、その例外をキャッチして正しい抽象概念を持った例外をスローしなおすべきです。

次のコードイディオムは「例外翻訳(exception translation)」と呼ばれています。

//
// 例外翻訳イディオム
//
try {
    // 下位レイヤの処理
    ... 
} catch (LowerLevelException e) {
    throw new HighLevelException(...);
}

たとえば、List インタフェースの骨格実装である AbstractSequentialList では get メソッドで実際に例外翻訳を行っています。

/**
 * このリストの指定された位置の要素を返す
 * @throws IndexOutOfBoundsException index が範囲外 ({@code index < 0 || index >= size()}).
 */
public E get(int index) {
    ListIterator<E> i = listIterator(index);
    try { 
        return i.next();
    } catch (NoSuchElementException e) {
        // 例外翻訳
        throw new IndexOUtOfBoundsException("index: " + index);
    } 
}

例外連鎖

上位レベルにスローする例外に、原因となる下位レベルの例外を持たせることができます。

これを例外連鎖(exception chaining)と呼びます。

//
// 例外連鎖イディオム
//
try {
   // 下位レイヤの処理
   ... 
} catch (LowerLevelException cause) {
   // 原因となった例外を HigherLevelException にセット
   throw new HigherLevelException(cause);
}

HigherLevelException のクラス定義は次のようになります。

class HigherLevelException extends Exception {
    HigherLevelException(Throwable cause) {
        super(cause);
    }
}

ほとんどの標準例外はこのコンストラクタを持っているため自身で定義する必要はありません。 このコンストラクタを持たない例外の場合、Throwable.initCause() を利用して、原因の例外を設定することができます。

セットされた原因の例外は Throwable.getCause() で取得することができます。

利用ケース

例外翻訳は乱用するべきではありません。

冒頭でも述べたとおり、何も考えずにすべての下位レイヤの例外を伝播させるよりはましですが、注意して利用するべきです。

まず最初に検討するべきなのは、下位レイヤのメソッドを呼び出す前にチェックメソッドなどを使ってメソッドの実行可否を判定することです。 そうすればそもそも下位レイヤの例外を発生させないですみます。

もし、チェックメソッドがない場合、できるならば下位レイヤの例外をキャッチして何かの処理するべきです。 たとえばログを出力し、例外を無視して上位レイヤの適切な処理を行う、などです。 そうすれば下位レイヤの問題を上位レイヤから隔離することができます。

それも不可能な場合で、かつ、上位レイヤに直接例外をスローすることが正しくない場合だけ、例外翻訳を使うべきです。