なぜ例外を再スローするのでしょうか?
-
02-07-2019 - |
質問
次のコードを何度も見てきました。
try
{
... // some code
}
catch (Exception ex)
{
... // Do something
throw new CustomException(ex);
// or
// throw;
// or
// throw ex;
}
例外を再スローする目的を説明していただけますか?例外処理のパターン/ベスト プラクティスに従っていますか?(これは「Caller Inform」パターンと呼ばれるものだとどこかで読んだことがありますか?)
解決
同じ例外を再スローすることは、たとえば、例外を処理せずにログに記録したい場合に便利です。
キャッチした例外をラップする新しい例外をスローすることは、抽象化に適しています。たとえば、ライブラリは、ライブラリのクライアントが認識すべきでない例外をスローするサードパーティ ライブラリを使用しています。その場合、それをライブラリにとってよりネイティブな例外型にラップし、代わりにそれをスローします。
他のヒント
実際には違いがあります
throw new CustomException(ex);
そして
throw;
2 つ目はスタック情報を保存します。
ただし、例外をアプリケーション ドメインにとってより「フレンドリー」なものにしたい場合は、DatabaseException を GUI に到達させる代わりに、元の例外を含むカスタム例外を発生させます。
例えば:
try
{
}
catch (SqlException ex)
{
switch (ex.Number) {
case 17:
case 4060:
case 18456:
throw new InvalidDatabaseConnectionException("The database does not exists or cannot be reached using the supplied connection settings.", ex);
case 547:
throw new CouldNotDeleteException("There is a another object still using this object, therefore it cannot be deleted.", ex);
default:
throw new UnexpectedDatabaseErrorException("There was an unexpected error from the database.", ex);
}
}
メソッドの実装の詳細を非表示にしたり、問題の抽象化のレベルを改善して、メソッドの発信者にとってより意味のあるものにすることもあります。これを行うには、元の例外をインターセプトし、問題を説明するのに適したカスタム例外を置き換えることができます。
たとえば、要求されたユーザーの詳細をテキスト ファイルからロードするメソッドを考えてみましょう。このメソッドは、ユーザーの ID と「.data」という接尾辞が付いた名前のテキスト ファイルが存在することを前提としています。そのファイルが実際には存在しない場合、FileNotFoundException をスローすることはあまり意味がありません。各ユーザーの詳細がテキスト ファイルに保存されているという事実は、メソッド内部の実装の詳細であるためです。したがって、このメソッドは代わりに、説明メッセージを含むカスタム例外で元の例外をラップすることができます。
表示されているコードとは異なり、ベスト プラクティスは、元の例外を新しい例外の InnerException プロパティとしてロードして保持することです。これは、開発者が必要に応じて根本的な問題を分析できることを意味します。
カスタム例外を作成する場合は、次の便利なチェックリストを参照してください。
• 例外がスローされた理由を伝える適切な名前を見つけて、その名前が「Exception」という単語で終わるようにしてください。
• 3 つの標準例外コンストラクターを必ず実装してください。
• 例外を Serializable 属性でマークしてください。
• 逆シリアル化コンストラクターを必ず実装してください。
• 開発者が例外をよりよく理解して処理できるようにするためのカスタム例外プロパティを追加します。
• カスタム プロパティを追加する場合は、必ず GetObjectData を実装およびオーバーライドしてカスタム プロパティをシリアル化してください。
• カスタム プロパティを追加する場合は、標準例外メッセージにプロパティを追加できるように、Message プロパティをオーバーライドします。
• カスタム例外の InnerException プロパティを使用して元の例外を忘れずに添付してください。
通常、コードがアプリケーション内のアーキテクチャ上のどこに位置するかに応じて、次の 2 つの理由のいずれかでキャッチして再スローします。
アプリケーションの中核では通常、例外をキャッチして再スローして、例外をより意味のあるものに変換します。たとえば、データ アクセス レイヤーを作成し、SQL Server でカスタム エラー コードを使用している場合は、SqlException を ObjectNotFoundException などに変換する可能性があります。これは、(a) 呼び出し元が特定の種類の例外を処理しやすくなる、および (b) 永続化に SQL Server を使用しているという事実など、その層の実装の詳細が他の層に漏洩するのを防ぐため、便利です。将来の変更をより簡単に行うことができます。
アプリケーションの境界では、例外を変換せずにキャッチして再スローするのが一般的です。これにより、例外の詳細をログに記録して、実際の問題のデバッグや診断に役立てることができます。理想的には、運用チームが簡単に監視できる場所にエラーを公開する必要があります (例:イベント ログ)のほか、制御フロー内で例外が発生した場所に関するコンテキストを開発者に提供する場所(通常はトレース)。
次のような理由が考えられます。
API の一部として、スローされる例外タイプのセットを固定したままにし、呼び出し元が考慮する必要があるのは、固定された例外セットのみです。Java では、チェック例外メカニズムにより、実際にはそうすることが強制されます。
例外にコンテキスト情報を追加します。たとえば、「レコードが見つかりません」というそのままの状態を DB から通過させる代わりに、それをキャッチして「...」を追加したい場合があります。注文番号 XXX を処理中、製品 YYY を探しています。」
ファイルを閉じ、トランザクションをロールバックし、一部のハンドルを解放するなど、クリーンアップを実行します。
一般に、「何かを行う」には、例外をより適切に説明する (たとえば、別の例外でラップする) か、特定のソースを通じて情報を追跡することが含まれます。
もう 1 つの可能性は、例外をキャッチする必要があるかどうかを判断するのに例外タイプだけでは十分な情報がない場合です。その場合、例外をキャッチして調べることで、より多くの情報が得られます。
これは、このメソッドが純粋に正当な理由で使用されるというわけではありません。多くの場合、開発者が将来のある時点でトレース情報が必要になる可能性があると考えるときにこのメソッドが使用されます。その場合、try {} catch {throw;} スタイルが得られます。まったく役に立ちません。
それは例外を使って何をしようとしているかによると思います。
適切な理由の 1 つは、最初にキャッチにエラーを記録し、それを UI にスローして、元のエラーを含むエラーのより「詳細な」ビューを表示するオプションを含む分かりやすいエラー メッセージを生成することです。 。
もう 1 つのアプローチは、「再試行」アプローチです。たとえば、エラー カウントが保持され、一定回数の再試行の後、そのときだけエラーがスタックに送信されます (これは、タイムアウトしたデータベース呼び出しのデータベース アクセスで行われる場合があります)。遅いネットワークを介して Web サービスにアクセスする場合)。
ただし、そうする理由は他にもたくさんあるでしょう。
参考までに、これは各タイプの再スローに関する関連質問です。例外をスローする場合のパフォーマンスに関する考慮事項
私の質問は、例外を再スローする「理由」と、アプリケーションの例外処理戦略におけるその使用法に焦点を当てています。
EntLib ExceptionBlock を使い始めるまでは、エラーをスローする前にエラーをログに記録するために使用していました。その時点でそれらを処理できたかもしれないと考えると、ちょっと厄介ですが、当時は、フローオンのバグをカバーするよりも、UAT で(ログを記録した後に)ひどい失敗をさせる方が良かったです。
アプリケーションはおそらくコール スタックの上位で再スローされた例外をキャッチするため、それらを再スローすることで上位のハンドラーがそれらをインターセプトし、適切に処理できるようになります。アプリケーションに、期待をログに記録したり報告したりするトップレベルの例外ハンドラーがあることは非常に一般的です。
もう 1 つの方法は、コーダーが怠惰で、処理したい例外のセットだけをキャッチするのではなく、すべてをキャッチし、実際に処理できない例外だけを再スローしたというものです。
Rafal が述べたように、これは、チェック例外を非チェック例外、または API により適したチェック例外に変換するために行われることがあります。ここに例があります:
http://radio-weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
例外を次のように見ると メソッドの結果を取得する別の方法, 、その後、例外を再スローすることは、結果を他のオブジェクトにラップすることに似ています。
そして、これは例外ではない世界では一般的な操作です。通常、これは 2 つのアプリケーション層の境界で発生します - 層からの関数が実行されるとき B
レイヤーから関数を呼び出します C
, 、変身します C
の結果は B
の内部形式。
A -- calls --> B -- calls --> C
そうでない場合は、レイヤーで A
レイヤーを呼び出すもの B
処理すべき JDK 例外の完全なセットが存在します。:-)
受け入れられた回答でも指摘されているように、レイヤー A
気づいていないかもしれない C
の例外です。
例
レイヤーA, 、サーブレット:画像とそのメタ情報を取得します
レイヤーB, 、JPEGライブラリ:JPEG ファイルを解析するために利用可能な DCIM タグを収集します
レイヤーC, 、単純な DB:ランダム アクセス ファイルから文字列レコードを読み取るクラス。一部のバイトが壊れているため、「レコード 'bibliographicCitation' の UTF-8 文字列を読み取れません」という例外がスローされます。
それで A
「参考文献引用」の意味がわかりません。それで B
この例外を次のように翻訳する必要があります A
の中へ TagsReadingException
オリジナルをラップします。
例外を再スローする主な理由は、呼び出しスタックをそのままにし、何が起こったのか、そして呼び出しシーケンスをより完全に把握できるようにすることです。