一般的な例外をキャッチするのは本当に悪いことですか?


57

私は通常、ほとんどのコード分析の警告に同意し、それらを順守しようとします。しかし、私はこれで苦労しています:

CA1031:一般的な例外タイプをキャッチしない

このルールの根拠を理解しています。しかし、実際には、スローされた例外に関係なく同じアクションを実行したい場合、それぞれを具体的に処理するのはなぜですか?さらに、特定の例外を処理する場合、呼び出しているコードが変更されて将来新しい例外がスローされるとどうなりますか?次に、その新しい例外を処理するためにコードを変更する必要があります。一方、単にExceptionコードをキャッチしただけなら、変更する必要はありません。

たとえば、FooがBarを呼び出し、FooがBarによってスローされた例外のタイプに関係なく処理を停止する必要がある場合、キャッチしている例外のタイプを特定することに利点はありますか?

たぶんより良い例:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

ここで一般的な例外をキャッチしない場合は、可能なあらゆる種類の例外をキャッチする必要があります。パトリックには、OutOfMemoryExceptionこの方法で対処すべきでない良い点があります。では、例外をすべて無視したい場合はどうしOutOfMemoryExceptionますか?


12
どうOutOfMemoryException?他のすべてと同じ処理コード?
パトリック

@パトリック良い点。あなたの質問をカバーするために、質問に新しい例を追加しました。
ボブ・ホーン

1
一般的な例外をキャッチすることは、誰もがすべきことについて一般的なステートメントを作成することと同じくらい悪いです。一般的に、すべてに万能のアプローチはありません。

4
@PatrickそれはOutOfMemoryError、まさにそのException理由で継承ツリーとは別のものです
-Darkhogg

Ctrl-Alt-Delのようなキーボードの例外はどうですか?
-Mawg

回答:


33

これらのルールは一般的に良いアイデアであり、従って従う必要があります。

しかし、これらは一般的なルールであることを忘れないでください。すべての状況をカバーしているわけではありません。それらは最も一般的な状況をカバーしています。特定の状況があり、テクニックが優れているという議論ができる場合(そして、そうするための議論を明確にするためにコードにコメントを書くことができるはずです)、そうする(そして、ピアレビューを取得する)ことができます。

引数の反対側。

上記の例は、そうするのに適した状況ではないと思います。ロギングシステムに障害が発生している場合(おそらく他の例外をロギングしている場合)、おそらくアプリケーションを続行したくないでしょう。終了し、例外を出力に出力して、ユーザーに何が起こったかを確認できるようにします。


2
同意します。非常に少なくとも、今では定期的なログがあげる規定のいくつかの種類作るオフラインになっているいくつかの何もない場合はSTDERRに、デバッグ出力の種類を。
シャドゥール

6
私はあなたの両方を支持しましたが、あなたはユーザーではなく開発者のように考えていると思います。通常、ユーザーは、アプリケーションが使用可能である限り、ロギングが発生しているかどうかを気にしません。
Mawg

1
興味深いことに、log4netは、ロギングが失敗したからといってアプリを停止したくないのです。そして、それはあなたが何を記録しているかに依存すると思います。セキュリティ監査は、確かに、プロセスを強制終了します。しかし、デバッグログ?それほど重要ではありません。
アンディ

1
@アンディ:うん、すべてはあなたがしていることに依存します。
マーティンヨーク

2
なぜあなたはそれらを理解しませんか?そして、あなたはそうするように努力すべきではありませんか?
-Mawg

31

はい、一般的な例外をキャッチするのは悪いことです。通常、例外とは、プログラムがユーザーが要求したことを実行できないことを意味します。

処理できる例外にはいくつかの種類があります。

  • 致命的な例外:メモリ不足、スタックオーバーフローなど。一部の超自然的な力があなたの宇宙を台無しにし、プロセスはすでに死にかけています。状況を改善することはできませんので、あきらめてください
  • 使用しているコードにバグがあるため、例外がスローされました。それらを処理しようとせずに、問題の原因を修正してください。フロー制御に例外を使用しないでください
  • 例外的な状況:これらの場合にのみ例外を処理します。ここに含めることができるのは、ネットワークケーブルが接続されていない、インターネット接続が機能しなくなっている、権限が欠落しているなどです。

ああ、そして一般的なルールとして:もしあなたがそれを捕まえて例外をどうするかわからないなら、ただ速く失敗するほうが良いです(呼び出し元に例外を渡し、それを処理させます)


1
質問の特定のケースはどうですか?ロギングコードにバグがある場合、例外を無視しても大丈夫です。実際に重要なコードはその影響を受けないためです。
svick

4
代わりにロギングコードのバグを修正してみませんか?
ビクターHurdugaci

3
ビクター、おそらくバグではない。ログが存在するディスクの容量が不足している場合、それはロギングコードのバグですか?
Carson63000

4
@ Carson63000-ええ、はい。したがって、ログは書き込まれていません。バグです。固定サイズの回転ログを実装します。
12

5
これが最良の答えです。しかし、重要な側面が1つ欠けています:セキュリティ。悪者がプログラムを実行しようとした結果として発生するいくつかの例外があります。処理できない例外がスローされると、必ずしもそのコードを信頼できなくなります。悪者を
入れる

15

一番外側のループには、これらのいずれかを表示して、可能な限りすべてを印刷し、恐ろしく暴力的で騒々しい死を遂げます(これは起こるべきではなく、誰かが聞く必要があるためです)。

それ以外の場合は、この場所で発生する可能性のあるすべてのことを予想していない可能性が高いため、通常は非常に注意する必要があります。できるだけ具体的に考えて、発生することがわかっている人だけをキャッチし、前に見られなかった人だけが上記の騒々しい死までバブルするようにします。


1
理論的にはこれに同意します。しかし、上記のロギングの例はどうですか?ロギング例外を無視して続行したい場合はどうでしょうか?スローされる可能性のあるすべての例外をキャッチし、それらのそれぞれを処理しながら、より深刻な例外をバブルアップさせる必要がありますか?
ボブ・ホーン

6
@BobHorn:それを見る2つの方法。はい、ロギングは特に重要ではなく、失敗してもアプリが停止することはありません。そして、それは公正な点です。しかし、アプリケーションが5か月間ログに記録できず、何もわからなかった場合はどうでしょうか?私はこれが起こるのを見ました。そして、ログが必要でした。災害。失敗をログに記録するのをやめるために考えられるすべてのことを行うことをお勧めします。(しかし、あなたはそれについて多くのコンセンサスを得るつもりはありません。)
pdr

@pdrログの例では、通常、電子メールアラートを送信します。または、ログを監視するためのシステムが用意されており、ログにアクティブにデータが入力されていない場合、サポートチームがアラートを受け取ります。そのため、他の方法でロギングの問題に気付くでしょう。
ボブ・ホーン

@BobHornあなたのロギングの例はかなり不自然です-私が知っているほとんどのロギングフレームワークは、例外スローしないように非常に長くなり、そのように呼び出されません(「ファイルYの行Xで発生したログステートメント」が役に立たない)

@pdr私はログバックリスト(Java)で、ロギング設定が失敗した場合にロギングフレームワークにアプリケーションを停止させる方法についての質問を提起しました。単にログを持っていることが非常に重要であり、サイレント障害は受け入れられないからです。良い解決策はまだ保留中です。

9

それは悪いことではなく、特定の漁獲量が優れているということです。 具体的に言うと、アプリケーションが何をしているかをより具体的に理解し、それをより細かく制御できることを意味します。 一般に、例外をキャッチし、ログに記録して続行するという状況に遭遇した場合、とにかくいくつかの悪いことが起こっている可能性があります。 コードブロックまたはメソッドがスローする可能性があることがわかっている例外を具体的にキャッチしている場合、ログを記録して最善を期待するのではなく、実際に回復できる可能性が高くなります


5

2つの可能性は相互に排他的ではありません。

理想的な状況では、メソッドが生成する可能性のあるすべての種類の例外をキャッチし、例外ごとに処理し、最後に一般的なcatch句を追加して、将来または未知の例外をキャッチします。これにより、両方の長所を最大限に活用できます。

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

より具体的な例外をキャッチするには、それらを最初に配置する必要があることに注意してください。

編集:コメントに記載されている理由により、最後のキャッチに再スローを追加しました。


2
待つ。なぜあなたはコンソールに未知の例外を記録し続けたいのでしょうか?コードによってスローされる可能性があることさえ知らなかった例外の場合は、おそらくシステム障害について話しているでしょう。このシナリオでの実行は、おそらく非常に悪い考えです。
PDR

1
@pdr確かに、あなたはそれが不自然な例であることを理解しています。重要なのは、特定の例外と一般的な例外の両方をキャッチすることであり、それらの処理方法ではありません。
ロテム

@Rotem良い答えがあると思います。しかし、pdrにはポイントがあります。コードはそのままで、キャッチして継続するのは問題ないように見えます。一部の初心者は、この例を見て、これが良いアプローチだと思うかもしれません。
ボブ・ホーン

工夫されていてもいなくても、答えが間違っています。Out Of MemoryやDisk Fullなどの例外のキャッチを開始し、それらを飲み込むとすぐに、一般的な例外をキャッチすることが実際に悪い理由がわかります。
PDR

1
ログに記録するためだけに一般的な例外をキャッチした場合は、ログに記録した後に例外を再スローする必要があります。
ビクターHurdugaci

5

私は最近同じことを熟考しており、私の暫定的な結論は、.NET Exception階層がひどく台無しになっているため、単なる質問が生じるということです。

たとえば、キャッチしたくない例外の妥当な候補でArgumentNullExceptionある可能性のある低レベルを考えてみましょ。これは、正当な実行時エラーではなくコードのバグを示す傾向があるためです。ああ、はい。またNullReferenceExceptionNullReferenceExceptionからSystemException直接派生することを除いて、キャッチする(またはキャッチしない)すべての「論理エラー」を配置できるバケットはありません。

その後 IMNSHOあり、主要な持つのやりそこなうSEHException(経由導出ExternalExceptionSystemExceptionので、それが「通常の」作るSystemExceptionあなたが得るときSEHException、あなたはダンプを書き込むとすることができますように早く終了したい- .NET 4で始まるとは、少なくとも一部 SEH例外は、キャッチされない破損状態の例外と見なされます。良いことであり、CA1031ルールがさらに役に立たないという事実catch (Exception)です。これは、あなたの怠け者がとにかくこれらの最悪のタイプをキャッチしないからです。

次に、他のフレームワークのものがException直接または経由で一貫性なく派生SystemExceptionしているようで、重大度のようなものでキャッチ節をグループ化しようとします。

氏リペットによって曲ありますC#名声は、と呼ばれる難問の例外、彼は例外のいくつかの有用な分類をレイアウト、:あなたは、あなただけを除いて、「外因性」のものをキャッチしたいと主張でき... C#言語と.NETのデザインフレームワークの例外により、簡潔な方法で「外生的なもののみをキャッチ」することができなくなります。(そして、例えば、OutOfMemoryExceptionあり非常によく何とか大きいバッファを割り当てなければならないAPIのための完全に正常な回復可能なエラーです)

私にとっては一番下の行は、ということ、であるC#のcatchブロックが仕事とフレームワークの例外階層が設計された方法は、ルールがCA1031全く役に立たを超えています。それはふり「例外を飲み込むていない」の根本問題を支援することではなく、例外を飲み込むと、あなたがキャッチ何を行うには、ゼロを持っていますが、あなたは何をして、その後の操作を行います。

あり、少なくとも 4つの方法合法的にキャッチを処理するためにはException、とCA1031のみ(すなわち再投ケース)表面的にそれらのいずれかを扱うようです。


注意点として、呼ばれるC#6の特徴があります例外フィルターになりますCA1031、あなたは正しく、正しく、適切にあなたがキャッチしたい例外をフィルタリングし、フィルタリングされていないの書き込みに少ない理由がありますすることができるようになりますので、再度、もう少し有効にcatch (Exception)


1
主要な問題OutOfMemoryExceptionは、特定の割り当ての失敗が失敗する準備ができていることを「のみ」示すことをコードが確実に確認できる良い方法がないことです。他のより重大な例外がスローされた可能性があり、OutOfMemoryExceptionスタックから他の例外を巻き戻している間に発生した可能性があります。Javaは「リソースを試してみる」ことでパーティーに遅れたかもしれませんが、スタックの巻き戻し中に.NETよりも少し優れた例外を処理します。
-supercat

1
@supercat-十分に真実ですが、最終ブロックで問題が発生した場合、他の一般的なシステム例外についても同様です。
マーティンBa

1
それは本当ですがfinally、元の例外と新しい例外の両方がログに記録されるような方法で実際に発生することが予想される例外からブロックを保護することができます(一般にそうすべきです)。残念ながら、それを行うには多くの場合メモリ割り当てが必要になり、それ自体の失敗を引き起こす可能性があります。
supercat

4

ポケモン例外処理(gotta catch em all!)は、必ずしも悪いわけではありません。メソッドをクライアント、特にエンドユーザーに公開するときは、アプリケーションをクラッシュさせたり焼き付けたりするよりも、何でもすべてをキャッチした方がよい場合がよくあります。

一般的には、できる限り避けるべきです。例外の種類に基づいて特定のアクションを実行できない限り、例外を飲み込んだり、誤って処理するのではなく、例外を処理せず、例外をバブルアップさせる方がよいでしょう。

詳細については、この SOの回答をご覧ください。


1
この状況(開発者がポケモントレーナーである必要がある場合)は、ソフトウェア全体を自分で作成するときに表示されます。レイヤー、データベース接続、ユーザーのGUIを作成しました。アプリは、接続の切断、ユーザーのフォルダーの削除、データの破損などの状況から回復する必要があります。エンドユーザーは、クラッシュして書き込みが発生するアプリを嫌います。彼らは燃えている爆発するクラッシュするコンピューターで動作するアプリが好きです!
Broken_Window 14

私はこれまでこれを考えていませんでしたが、ポケモンでは、一般的に「すべてをキャッチ」することによって、あなたはそれぞれの1つを正確にキャッチし、具体的にキャッチすることを処理しなければならないと想定されていますプログラマーの間。
メイガス14

2
@Magus:のようなメソッドではLoadDocument()、間違っている可能性のあるすべてのものを識別することは本質的に不可能ですが、スローされる可能性のある例外の99%は、単に「与えられた名前のファイルの内容を文書;それに対処する。」誰かが有効なドキュメントファイルではないものを開こうとしても、アプリケーションがクラッシュしたり、開いている他のドキュメントを強制終了したりすることはありません。そのような場合のポケモンエラー処理は見苦しいですが、私は良い代替手段を知りません。
supercat 14

@supercat:私はちょうど言語的なポイントを述べていました。ただし、無効なファイルの内容は、複数の種類の例外をスローするようなものとは思わない。
メイガス14

1
@Magus:それはあらゆる種類の例外を投げることができます-それが問題です。ファイル内の無効なデータの結果としてスローされる可能性のあるすべての種類の例外を予測することは、しばしば非常に困難です。PokeMon処理を使用しない場合、たとえば、ロードするファイルに10進形式の32ビット整数が必要な場所に非常に長い数字の文字列が含まれていて、解析ロジック。
supercat

1

一般的な例外をキャッチすると、プログラムが未定義の状態のままになるため、悪いです。どこで問題が発生したかわからないので、プログラムが実際に何をしたか、または何をしていないかがわかりません。

すべてをキャッチできるのは、プログラムを閉じるときです。それをきれいにできる限り。閉じたプログラムほど迷惑なことはありません。エラーダイアログが表示されるだけで、そこに座っているだけで、離れずにコンピューターが終了するのを防ぎます。

分散環境では、ログメソッドが裏目に出る可能性があります。一般的な例外をキャッチすると、プログラムがログファイルのロックを保持しているため、他のユーザーがログを作成できなくなります。


0

この規則の根拠を理解しています。しかし、実際には、スローされた例外に関係なく同じアクションを実行したい場合、それぞれを具体的に処理するのはなぜですか?

他の人が言ったように、スローされた例外に関係なく実行したいアクションを想像することは(不可能ではないにしても)本当に難しいです。ほんの一例は、プログラムの状態が破損しており、それ以上の処理が問題を引き起こす可能性がある状況です(これは背後にある理論的根拠ですEnvironment.FailFast)。

さらに、特定の例外を処理する場合、呼び出しているコードが変更されて将来新しい例外がスローされるとどうなりますか?次に、その新しい例外を処理するためにコードを変更する必要があります。一方、単に例外をキャッチした場合、コードを変更する必要はありません。

趣味のコードの場合はキャッチしても問題Exceptionありませんが、新しい例外タイプを導入するプロ級のコードの場合は、メソッドシグネチャの変更と同じ点で処理する必要があります。この観点にサブスクライブする場合、クライアントコードの変更(または検証)に戻ることが唯一の正しい行動であることはすぐに明らかです。

たとえば、FooがBarを呼び出し、FooがBarによってスローされた例外のタイプに関係なく処理を停止する必要がある場合、キャッチしている例外のタイプを特定することに利点はありますか?

確かに、によってスローされBarた例外だけをキャッチするわけではないからです。また、BarのクライアントまたはランタイムでさえBar、コールスタック上にある間にスローする例外があります。作成Bar者は、必要に応じて独自の例外タイプを定義する必要があります。これにより、呼び出し元は、自分で発行した例外を明確にキャッチできます。

では、OutOfMemoryException以外のすべての例外を無視したい場合はどうすればよいでしょうか?

私見これは例外処理について考える間違った方法です。ブラックリスト(X以外のすべての例外をキャッチ)ではなく、ホワイトリスト(例外タイプAおよびBをキャッチ)を操作する必要があります。


多くの場合、試行された操作が副作用なしで失敗したことを示すすべての例外に対して実行されるアクション、またはシステムの状態に関する悪影響を指定することが可能です。たとえば、ユーザーがロードするドキュメントファイルを選択すると、プログラムはその名前を「ディスクファイルからのデータを使用してドキュメントオブジェクトを作成する」メソッドに渡します。そのメソッドは、アプリケーションが予期していなかった何らかの理由で失敗します(ただし、上記の基準を満たしている場合)、適切な動作は、アプリケーションをクラッシュさせるのではなく、「ドキュメントを読み込めません」というメッセージを表示することです。
supercat

残念ながら、クライアントコードが知りたい最も重要なことを例外が示すことができる標準的な手段はありません-要求された操作を実行できないことによって暗示されるものを超えて、システムの状態について何か悪いことを意味するかどうか。したがって、すべての例外を処理する必要がある状況はまれですが、多くの例外を同じ方法で処理する必要がある多くの状況があり、そのような例外すべてが満たす基準はありません。別の方法で処理する必要があります。
-supercat

0

たぶん。すべてのルールには例外があり、ルールに無批判に従うことはできません。あなたの例では、可能性が考えられることは、すべての例外を飲み込むことは理にかなってインスタンスの一つです。たとえば、重要な実稼働システムにトレースを追加する場合、変更によってアプリのメインタスクが中断されないことを確実に確認したい場合です。

ただし、失敗の原因をすべて黙って無視する前に、考えられる失敗の理由について慎重に検討する必要があります。たとえば、例外の理由が次の場合はどうなりますか?

  • 常に特定の例外をスローするロギングメソッドのプログラミングエラー
  • 構成内のログファイルへの無効なパス
  • ディスクがいっぱいです

この問題が存在することをすぐに通知したくないので、修正できますか?例外を飲み込むことは、何かがうまくいかなかったことを決して知らないことを意味します。

一部の問題(ディスクがいっぱいなど)によって、アプリケーションの他の部分が失敗することもありますが、この失敗は現在ログに記録されていないため、わかりません!


0

技術的な観点ではなく論理的な観点からこれに対処したいと思います。

次に、その新しい例外を処理するためにコードを変更する必要があります。

まあ、誰かがそれを処理する必要があります。それがアイデアです。ライブラリコードの作成者は、新しい例外タイプを追加することを慎重に考えますが、クライアントが破損する可能性があるため、これを頻繁に発生させることはありません。

あなたの質問は基本的に「何が間違っているのか気にしないとしたら、それが何だったのかわからないのですか?」ということです。

ここに美しさの部分があります:いいえ、あなたはしません。

「それで、私はただ他の見方をして、どんな厄介なものが現れても自動的にカーペットの下に流されて、それで終わらせることができますか?」

いいえ、それは動作しません。

ポイントは、可能な例外のコレクションは常に期待するコレクションよりも大きく、ローカルの小さな問題のコンテキストに関心があるということです。予期したものを処理し、予期しないことが発生した場合は、それを飲み込むのではなく、より高いレベルのハンドラーに任せます。予期しない例外を気にしない場合は、コールスタックの誰かを賭けます。ハンドラーに到達する前に例外を強制終了するのは妨害になります。

「しかし...しかし...私が気にしない他の例外の1つは私のタスクを失敗させる可能性があります!」

はい。ただし、それらはローカルで処理されるものよりも常に重要です。火災警報器や上司があなたがしていることをやめて、現れたより緊急のタスクを拾うように言っているように。


-3

すべてのプロセスの最上位で一般的な例外をキャッチし、バグを可能な限り報告し、プロセスを終了することで処理する必要があります。

一般的な例外をキャッチして実行を継続しないでください。

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