例外の伝播:例外をキャッチするタイミング


44

MethodAはMethodBを呼び出し、MethodBはMethodCを呼び出します。

MethodBまたはMethodCには例外処理はありません。ただし、MethodAには例外処理があります。

MethodCでは、例外が発生します。

現在、この例外はMethodAにバブリングされており、適切に処理されます。

これの何が問題になっていますか?

私の考えでは、ある時点で呼び出し元がMethodBまたはMethodCを実行し、それらのメソッドで例外が発生した場合、それらのメソッド内で例外を処理することで得られるものは、本質的にletではなくtry / catch / finallyブロックですそれらは呼び出し先までバブルアップしますか?

例外処理に関するステートメントまたはコンセンサスは、それだけで実行が継続できない場合にスローすることです-例外。わかった。しかし、try / catchブロックを完全にダウンさせるのではなく、チェーンのさらに上で例外をキャッチしてください。

リソースを解放する必要があるときに理解しています。それはまったく別の問題です。


46
なぜコンセンサスはパススルーキャッチのチェーンを持つことだと思いますか?
カレス

優れたIDEと適切なコーディングスタイルを使用すると、メソッドが呼び出されたときに例外がスローされる可能性があることがわかります。それを処理するか、それが伝播されることを許可するのは、呼び出し側の決定です。私はこれに関して何の問題も見ていません。
Hieu Le

14
メソッドが例外を処理できず、単に例外を再スローしているだけなら、それはコードの匂いだと思います。メソッドが例外を処理できず、例外がスローされたときに他に何もする必要がない場合、try-catchブロックはまったく必要ありません。
グレッグブルクハルト

7
「これの何が問題なの?」:なし
ユアン

5
パススルーキャッチ(例外をさまざまな種類またはそのようなものでラップしない)は、例外の目的全体を無効にします。例外のスローは複雑なメカニズムであり、意図的に作成されました。パススルーキャッチが意図されたユースケースである場合、必要なのはResult<T>型(計算の結果またはエラーのいずれかを格納する型)を実装し、それ以外のスロー関数からそれを返すことだけです。エラーをスタックに伝播するには、すべての戻り値を読み取り、エラーであるかどうかを確認し、エラーがある場合はエラーを返します。
アレクサンダー

回答:


139

一般的な原則として、例外の処理方法がわからない限り、例外をキャッチしないでください。MethodCが例外をスローしたが、MethodBがそれを処理するための有用な方法を持たない場合、例外がMethodAまで伝播できるようにする必要があります。

メソッドにcatchおよびrethrowメカニズムが必要な唯一の理由は次のとおりです。

  • ある例外を、上記の呼び出し元にとってより意味のある別の例外に変換する必要があります。
  • 例外に追加情報を追加する必要があります。
  • リソースがないとリークするリソースをクリーンアップするには、catch句が必要です。

そうしないと、間違ったレベルで例外をキャッチすると、呼び出し元のコード(および最終的にはソフトウェアのユーザー)に有益なフィードバックを提供せずに、暗黙的に失敗するコードが発生する傾向があります。例外をキャッチしてすぐに再スローするという代替手段は無意味です。


28
お使いの言語がに似何かを持っている場合@GregBurghardtはtry ... finally ...、そのを使用し、キャッチしないと再スロー
Caleth

19
「例外をキャッチしてすぐに再スローするのは無意味です」言語とこれにどう対処するかによっては、コードベースに積極的に有害になる可能性があります。多くの場合、これを試みる人は、元のスタックトレースなど、例外に関する多くの情報を削除します。呼び出し元が、何がどこで発生したのかを完全に誤解させる例外を受け取るコードを処理しました。
ジミージェームズ

7
「例外の処理方法がわからない限り、例外をキャッチしないでください」。これは一見合理的ですが、後で問題が発生します。ここで行っているのは、呼び出し元に実装の詳細を漏らすことです。実装で特定のORMを使用してデータをロードするとします。そのORMの特定の例外をキャッチせず、単にそれらをバブルアップさせた場合、既存のユーザーとの互換性を損なうことなくデータレイヤーを置き換えることはできません。これは、より明白なケースの1つですが、かなり潜行性になり、キャッチするのは困難です。
Voo

11
あなたの例では@Voo それをどうするか知っています。コードに固有の文書化された例外でそれをラップします。たとえば、言語機能に応じて元の例外の詳細LoadDataException含めます。これにより、将来のメンテナーはデバッガをアタッチせずに問題を再現する方法を理解しなくても根本原因を確認できます。
コリンヤング

14
@Vooキャッチ/再スローのシナリオの理由を「上記の呼び出し元にとってより意味のある別の例外に変換したい」という理由を逃したようです。
jpmc26

21

これは何が問題なのですか?

何もない。

現在、この例外はMethodAにバブリングされており、適切に処理されます。

「適切に処理する」ことが重要な部分です。これが、構造化例外処理の核心です。

コードが例外を使用して「有用な」何かを実行できる場合は、それを実行します。そうでない場合は、おまかせください。

。。。try / catchブロックを完全にダウンさせるのではなく、チェーンの上位で例外をキャッチしてください。

それがまさにあなたがすべきことです。ハンドラー/リスローアーが「最後まで」あるコードを読んでいる場合、かなり悪いコードを(おそらく)読んでいることになります。

悲しいことに、一部の開発者は、キャッチブロックを、作成するすべてのメソッドにスローする「ボイラープレート」コードと見なします。その例外は、プログラムを「エスケープ」して強制終了することはありません。

ここでの難しさの一部は、ほとんどの場合、例外が常にスローされているわけではないため、この問題が気付かないことです。しかし、例外が発生すると、プログラムは非常に多くの時間を無駄にしコールスタックの選択を徐々に解除して、実際に例外に役立つ何かを実行する場所にたどり着くまでの努力。


7
さらに悪いのは、アプリケーションが例外をキャッチし、それをログに記録し(永久にそこにとどまらないことを願っています)、本当にできない場合でも通常どおり続行しようとすることです。
ソロモンウッコ

1
@SolomonUcko:まあ、それは異なります。たとえば、単純なRPCサーバーを記述しており、未処理の例外がメインイベントループまでバブルする場合、唯一の妥当なオプションは、それをログに記録し、RPCエラーをリモートピアに送信し、イベントの処理を再開することです。もう1つの方法は、プログラム全体を強制終了することです。これにより、サーバーが実稼働中に停止したときにSREが起動します。
ケビン

@Kevinその場合、catchエラーをログに記録し、エラー応答を返す可能な限り高いレベルのシングルが必要です。catchどこにでも散らばっていないブロック。すべてのチェック済み例外(Javaなどの言語)を一覧表示したくない場合はRuntimeException、ログに記録する代わりにラップして、続行しようとし、さらにエラーや脆弱性に陥ります。
ソロモンウッコ

8

ライブラリとアプリケーションを区別する必要があります。

ライブラリはキャッチされない例外を自由にスローできます

ライブラリを設計するとき、ある時点で何がうまくいかないかを考えなければなりません。パラメーターの範囲が間違っているかnull、外部リソースが利用できないなどの可能性があります。

あなたのライブラリは、ほとんどの場合、賢明な方法でそれらに対処する方法を持っていません。唯一の賢明な解決策は、適切な例外をスローし、アプリケーションの開発者にそれを処理させることです。

アプリケーションは常にある時点で例外をキャッチする必要があります

例外がキャッチされると、それらをエラーまたは致命的なエラーのいずれかに分類したいと思います。通常のエラーは、アプリケーション内の単一の操作が失敗したことを意味します。たとえば、宛先が書き込み可能ではないため、開いているドキュメントを保存できませんでした。アプリケーションが行うべき唯一の賢明な考えは、操作が正常に完了できなかったことをユーザーに通知し、問題に関して人間が読める情報を提供し、ユーザーに次に何をすべきかを決定させることです。

致命的なエラーは、メインのアプリケーションロジックから回復できないエラーです。たとえば、ビデオゲームでグラフィックスデバイスドライバーがクラッシュした場合、アプリケーションがユーザーに "優雅に"通知する方法はありません。この場合、ログファイルを作成し、可能であれば、何らかの方法でユーザーに通知する必要があります。

このような深刻な場合でも、アプリケーションはこの例外を有意義な方法で処理する必要があります。これには、ログファイルの書き込み、クラッシュレポートの送信などが含まれる場合があります。アプリケーションが何らかの方法で例外に応答しない理由はありません。


確かに、ディスク書き込み操作や他のハードウェア操作などのライブラリがある場合、あらゆる種類の予期しないイベントが発生する可能性があります。書き込み中にハードドライブが引っ張られた場合はどうなりますか?読み取り中にCDドライブが不足しますか?それはあなたのコントロールの外であり、あなた何かをすることができた間(例えば、それが成功したふりをする)、ライブラリユーザーに例外を投げて彼らに決定させるのはしばしば良い習慣です。たぶんa HDDPluggedOutDuringWritingExceptionは処理でき、アプリケーションにとって致命的ではありません。プログラムはそれをどうするかを決定できます。
VLAZ

1
@VLAZ致命的か非致命的かは、アプリケーションが決定しなければならないものです。ライブラリは何が起こったかを伝える必要があります。アプリケーションは、それにどのように反応するかを決定する必要があります。
MechMK1

0

説明するパターンの何が問題なのかというと、メソッドAには3つのシナリオを区別する方法がないためです。

  1. 方法Bは予期された方法で失敗しました。

  2. 方法Cは、方法Bでは予期されなかった方法で失敗しましたが、方法Bは安全に破棄できる操作を実行していました。

  3. 方法Cは、方法Bでは予期されなかった方法で失敗しましたが、方法Bが、Cの失敗のためにBがクリーンアップに失敗したと思われる一時的なインコヒーレント状態にする操作を実行していました。

メソッドAがこれらのシナリオを区別できる唯一の方法は、Bからスローされた例外にその目的に十分な情報が含まれている場合、またはメソッドBのスタックアンワインドによりオブジェクトが明示的に無効化された状態のままになる場合です。残念ながら、ほとんどの例外フレームワークは両方のパターンを扱いにくくし、プログラマーに「より悪い」設計上の決定を強いることになります。


2
シナリオ2と3はメソッドBのバグです。メソッドAはそれらを修正しようとしてはなりません。
シェード

@Sjoerd:メソッドBは、メソッドCが失敗する可能性のあるすべての方法をどのように予測するのですか?
supercat

一時変数にスローされる可能性のあるすべての操作を実行するなどのよく知られたパターンによって、スローできない操作(スワップなど)を使用して、古い状態を新しい状態にスワップします。別のパターンは、安全に繰り返すことができる操作を定義することです。そのため、混乱することを恐れずに操作を再試行できます。「例外安全コード」の記述に関する完全な本があるので、ここですべてを説明することはできません。
シェード

これは、例外をまったく使用しないための良い点です(これは、優れた決定方法です)。しかし、OPはそもそも例外を使用することを意図しているように見え、キャッチがどこにあるべきかを尋ねるだけなので、実際には質問には答えません。
cmaster

@Sjoerd Method Bを使用すると、言語で例外が禁止されているかどうかを簡単に判断できます。その場合、実際にはBを通るすべての制御フローパスが表示され、シナリオ3を回避するためにどの演算子がスロー方式(C ++)でオーバーロードされる可能性があるかを2番目に推測する必要がないためです。例外をスローするだけでエラーを返すのが面倒になるための明快さと安全性。結局のところ、エラー処理コードの重要な部分だからです。
cmaster
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.