例外を再スローすると抽象化が漏れますか?


12

特定の種類の例外をスローすることをドキュメントに記載するインターフェイスメソッドがあります。そのメソッドの実装は、例外をスローするものを使用します。内部例外がキャッチされ、インターフェイスコントラクトによって宣言された例外がスローされます。ここに、よりよく説明するための小さなコード例を示します。これはPHPで書かれていますが、簡単に理解できます。

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

抽象化のリークを防ぐために、このメソッドを使用しています。

私の質問は次のとおりです。前の例外が抽象化をリークしているので、スローされた内部例外を渡すでしょうか?そうでない場合、前の例外のメッセージを単に再利用するのが適切でしょうか?抽象化が漏れている場合、なぜそれが起こると思うかについてのガイダンスを提供できますか?

明確にするために、私の質問には次のコード行への変更が含まれます

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);

例外は誰がそれらを投げるかでなく原因についてのはずです。コードにを含めることができる場合は、をスローする必要があります。例外はライブラリ固有ではありませんfile_not_foundfile_not_found_exception
Mooingダック14

回答:


11

私の質問は次のとおりです。前の例外が抽象化をリークしているので、スローされた内部例外を渡すでしょうか?そうでない場合、前の例外のメッセージを単に再利用するのが適切でしょうか?

答えは「依存する」です。

具体的には、例外とその意味に依存します。本質的に、それを再スローすると、それをインターフェースに追加していることになります。自分で呼び出しているコードを書いた場合、それを行うのですか、それとも例外のブランド変更を避けるためですか?

例外に、呼び出されたコードの根本原因までのトレースが含まれている場合、抽象化の外にデータが漏れる可能性がありますが、一般的には、例外は呼び出し元によって処理されるため、受け入れられると思います(誰もそれを気にしないから来た)またはユーザーに表示されます(デバッグのために根本原因を知りたい)

ただし、多くの場合、例外の通過を許可することは実装の非常に合理的な選択であり、抽象化の問題ではありません。「私が呼び出しているコードがそうでなかった場合、私はこれを投げますか?」


8

厳密に言えば、「内部」例外が実装の内部動作に関する何かを明らかにする場合、あなたリークしています。技術的には、常にすべてをキャッチして、独自の例外タイプをスローする必要があります。

だが。

これに対して非常に少数の重要な議論をすることができます。最も注目すべきは:

  • 例外は例外的なものです。すでに多くの点で「通常の操作」のルールに違反しているため、カプセル化やインターフェースレベルの抽象化にも違反する可能性があります。
  • 意味のあるスタックトレースその場でのエラー情報を持つことの利点は、UIバリアを越えて内部プログラム情報を漏らさない限り(情報漏えい)、OOPの正確性を上回ることがよくあります。
  • 適切な外部例外タイプですべての内部例外をラップすると、多くの無駄な定型コードが発生する可能性があり、例外の目的全体を無効にします(つまり、エラー処理ボイラープレートで通常のコードフローを汚染することなくエラー処理を実装します)。

ただし、ログに追加情報を追加したり、内部情報がエンドユーザーに漏洩したりするのを防ぐためだけであれば、例外をキャッチしてラップすること理にかなってます。エラー処理は依然として芸術の形式であり、いくつかの一般的なルールに要約するのは困難です。一部の例外はバブルする必要があり、他の例外はバブルしない必要があります。また、決定はコンテキストにも依存します。UIをコーディングしている場合、ユーザーにフィルターをかけずに何もさせたくないでしょう。しかし、何らかのネットワークプロトコルを実装するライブラリをコーディングしている場合、特定の例外をバブリングすることはおそらく正しいことです(結局、ネットワークがダウンしている場合、情報を強化したり回復して継続するためにできることはほとんどありません) ;翻訳NetworkDownExceptionすることFoobarNetworkDownException)だけでは無意味嫌なものです。


6

まず、ここでやっていることは、例外を再スローすることではありません。再スローすると、次のように文字通り同じ例外がスローされます。

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

明らかに、これは少し無意味です。再スローとは、例外がスタックをバブリングするのを止めることなく、リソースを解放したり、例外またはその他の処理をログに記録したりする状況向けです。

あなたがすでにしていることは、原因が何であれ、すべての例外をDoohickeyDisasterExceptionに置き換えることです。あなたがやろうとしていることかもしれませんし、そうでないかもしれません。しかし、スタックトレースで役立つ場合に備えて、これを行うときはいつでも、提案どおりに実行し、元の例外を内部例外として渡すことをお勧めします。

しかし、これは漏れやすい抽象化に関するものではなく、別の問題です。

再スローされた例外(または実際にキャッチされなかった例外)がリークのある抽象化になり得るかどうかの質問に答えるために、それは呼び出し元のコードに依存すると言います。そのコードが抽象化されたフレームワークの知識を必要とする場合、そのフレームワークを別のフレームワークに置き換える場合、抽象化の外側でコードを変更する必要があるため、漏れやすい抽象化です。

たとえば、DoohickeyDisasterExceptionがフレームワークからのクラスであり、呼び出しコードが次のようになっている場合、リークのある抽象化があります。

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

サードパーティのフレームワークを別の例外名を使用する別のフレームワークに置き換える場合、これらすべての参照を見つけて変更する必要があります。独自のExceptionクラスを作成して、その中にフレームワーク例外をラップすることをお勧めします。

一方、DoohickeyDisasterExceptionが独自の作成の1つである場合、抽象化をリークしていないので、私の最初のポイントにもっと関心を持つ必要があります。


2

私がしようとしているルールは、あなたがそれを引き起こした場合、それをラップします。同様に、ランタイム例外のためにバグが作成された場合、それは何らかのDoHickeyExceptionまたはMyApplicationExceptionである必要があります。

それが意味するのは、エラーの原因がコードの外部にあるものであり、失敗することを期待していない場合、正常に失敗して再スローすることです。同様に、コードに問題がある場合は、(潜在的に)カスタム例外でラップします。

たとえば、ファイルがディスク上にあると予想される場合、またはサービスが利用できると予想される場合は、コードを利用するユーザーがそのリソースが利用できない理由を把握できるように、適切に処理してエラーを再スローします。あるタイプに対して作成したデータが多すぎる場合、ラップしてスローするのはエラーです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.