web-dev-qa-db-ja.com

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

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

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

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

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

多分より良い例:

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.
    }
}

ここで一般的な例外をキャッチしない場合は、可能なすべてのタイプの例外をキャッチする必要があります。 Patrickは、OutOfMemoryExceptionがこの方法で処理されるべきではないという良い点を持っています。では、OutOfMemoryException以外のすべての例外を無視したい場合はどうなりますか?

59
Bob Horn

これらのルールは一般的にが適切であるため、従う必要があります。

しかしこれらは一般的な規則であることを覚えておいてください。すべての状況を網羅しているわけではありません。彼らは最も一般的な状況をカバーしています。特定の状況があり、テクニックが優れていると主張できる場合(そして、コードにコメントを記述して、そうするための引数を明確に示すことができるはずです)、そうする(そして、ピアレビューを受けてください)。

議論の反対側。

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

33
Martin York

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

couldが処理する例外にはいくつかのタイプがあります。

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

ああ、そして一般的なルールとして:例外をキャッチしても何をすべきかわからない場合は、すぐに失敗する方が良い(例外を呼び出し元に渡し、処理させる)

31

一番上の外側のループには、可能な限りすべてを出力するためにこれらの1つが必要です。そして、恐ろしく、暴力的で、騒々しい死を迎えます(これは起こり得ないことであり、誰かが聞く必要があるため)。

それ以外の場合は、この場所で発生する可能性のあるすべてのことをnotと予想し、そのため、正しく処理されない可能性が高いため、一般に非常に注意する必要があります。 知っているが発生する可能性のあるものだけをキャッチして、上記の騒々しい死まで泡立つ前に見られなかったものだけをキャッチできるように、できるだけ具体的にしてください。

15
user1249

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

11
Ryan Hayes

私は最近同じことを考えていますが、私の暫定的な結論は、 。NET例外階層 がひどくめちゃくちゃになっているため、単なる質問が生じるということです。

たとえば、 ArgumentNullException のように、mightが例外の妥当な候補になりますしないでください-tendsは、正当な実行時エラーではなくコード内のバグを示すため、キャッチする必要があります。ああそう。 NullReferenceException も例外です。ただし、NullReferenceExceptionSystemExceptionから直接派生するため、すべての「論理エラー」をキャッチして(またはキャッチせずに)配置できるバケットはありません。

次にIMNSHOがありますmajorSEHException の派生のボット(ExternalExceptionを使用)SystemExceptionを「通常の」SystemExceptionにする SEHExceptionを取得したら、ダンプを書き込んでできるだけ早く終了させたいと思います-.NET 4以降では、少なくともsomeSEH例外は考慮されます キャッチされない破損状態の例外 。良いことであり、_CA1031_ルールがさらに役に立たなくなるという事実です。これは、怠惰なcatch (Exception)がこれらの最悪の型をとにかくキャッチしないためです。

次に、他のフレームワークのものは、ExceptionSystemExceptionを介して直接またはのいずれかから一貫性なく派生させ、重大度のモットのようなものによってキャッチ句をグループ化しようとします。

Vexing Exception と呼ばれる_C#_名声のLippert氏による一片があり、彼は例外のいくつかの有用な分類を示しています。 "...以外のもの... _C#_言語と.NETフレームワークの例外の設計により、簡潔な方法で「外因性のものだけをキャッチ」することが不可能になります。 (そして、例えば、OutOfMemoryExceptionvery wellとにかく大きいバッファを割り当てる必要があるAPIの完全に正常な回復可能なエラーである可能性があります)

私の結論は、_C#_ catchブロックが機能する方法と、フレームワークの例外階層が設計されている方法、ルール_CA1031_はまったく役に立たないpretendsは、「例外を飲み込まない」という根本的な問題を解決しますが、例外を飲み込むことは、捕捉することとは関係ありませんが、その後実行:

キャッチされたExceptionを正当に処理するための 少なくとも4つの方法 があり、_CA1031_は表面的にのみ処理するようですそれらの(つまり、再スローの場合)。


補足として、 Exception Filters と呼ばれるC#6機能があります。これにより、_CA1031_が再び有効になります。これにより、適切に、適切に、適切にフィルターをかけることができます。あなたがキャッチしたい例外、そしてフィルタリングされていないcatch (Exception)を書く理由はそれほどありません。

5
Martin Ba

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;
}

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

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

5
Rotem

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

一般に、できる限り回避する必要があります。例外のタイプに基づいて特定のアクションを実行できない場合を除き、例外を処理しないで、例外を飲み込んだり、誤って処理したりせずに、例外がバブルアップするようにしてください。

詳しくは this SO answerをご覧ください。

4
Tom Squires

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

すべてをキャッチできるのは、プログラムを閉じるときです。あなたがそれを申し分なくきれいにすることができる限り。プログラムを閉じて、そこに座っているだけのエラーダイアログをスローするだけの煩わしいものはありません。

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

1
Pieter B

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

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

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

あなたの質問は、基本的には「何が問題だったか気にしない場合はどうなりますか?それが何であるかを見つけるのに面倒なことを本当にしなければならないのですか?」です。

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

「だから、私はただ逆に見て、どんな厄介なことがポップアップでカーペットの下に自動的に吹き込まれ、それで終了することができますか?」

いいえ、それも機能しません。

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

「しかし...しかし...私が気にしていない他の例外の1つが原因で、タスクが失敗する可能性があります。」

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

0
Martin Maat

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

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

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

趣味のコードの場合、Exceptionをキャッチしても問題ありませんが、プロ仕様のコードの場合、新しい例外タイプを導入することは、メソッドシグネチャの変更と同じように扱う必要があります。つまり、重大な変更と見なされます。この視点にサブスクライブすると、クライアントコードを変更(または検証)することに戻ることが、唯一正しい行動方針であることはすぐにわかります。

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

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

では、OutOfMemoryException以外のすべての例外を無視したい場合はどうなりますか?

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

0
Jon

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

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

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

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

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

0
JacquesB