119

現在、最初の Windows フォーム アプリケーションを作成中です。C# の本を何冊か読んだので、C# が例外を処理するために必要な言語機能を比較的よく理解できました。ただし、それらはすべて非常に理論的なものであるため、基本的な概念をアプリケーションで優れた例外処理モデルに変換する方法については、まだ感じていません。

この件に関する知恵の真珠を共有したい人はいますか? 私のような初心者が犯すよくある間違いと、アプリケーションをより安定して堅牢にする方法で例外を処理するための一般的なアドバイスを投稿してください。

私が現在解決しようとしている主なことは次のとおりです。

  • いつ例外を再スローする必要がありますか?
  • ある種の中心的なエラー処理メカニズムを持つべきですか?
  • スローされる可能性のある例外を処理すると、ディスク上のファイルが存在するかどうかなどを事前にテストする場合と比較して、パフォーマンスが低下しますか?
  • すべての実行可能コードを try-catch-finally ブロックで囲む必要がありますか?
  • 空の catch ブロックが許容される場合はありますか?

すべてのアドバイスに感謝します!

4

16 に答える 16

83

あと数ビット...

一元化された例外処理ポリシーを導入する必要があります。これは、try/catch でラップするのと同じくらい簡単Main()で、ユーザーに適切なエラー メッセージを表示してすばやく失敗します。これは「最後の手段」の例外ハンドラです。

プリエンプティブ チェックは、実行可能であれば常に正しいものですが、常に完全であるとは限りません。たとえば、ファイルの存在を確認するコードとそれを開く次の行の間で、ファイルが削除されているか、他の問題によってアクセスが妨げられている可能性があります。その世界ではまだtry/catch/finallyが必要です。必要に応じて、プリエンプティブ チェックと try/catch/finally の両方を使用します。

スローされた例外が存続可能であることを絶対に確実に確信している、最もよく文書化されたケースを除いて、例外を「飲み込む」ことは絶対にしないでください。これはほとんどありません。(そうである場合は、特定の例外クラスのみを飲み込んでいることを確認してください。決して飲み込まないでくださいSystem.Exception。)

(アプリで使用される) ライブラリをビルドするときは、例外を飲み込まず、例外が発生するのを恐れないでください。追加するのに役立つものがない限り、再スローしないでください。これを (C# で) 絶対にしないでください。

throw ex;

コールスタックを消去します。再スローする必要がある場合 (Enterprise Library の例外処理ブロックを使用する場合など、必要になる場合があります)、次を使用します。

throw;

結局のところ、実行中のアプリケーションによってスローされる例外の大部分は、どこかに公開する必要があります。それらはエンドユーザーに公開されるべきではありません (多くの場合、機密データやその他の重要なデータが含まれているため)。むしろ、通常はログに記録され、管理者に例外が通知されます。単純化するために、一般的なダイアログ ボックス (参照番号など) をユーザーに表示することができます。

.NET での例外処理は、科学というより芸術です。誰もがここで共有するお気に入りを持っています。これらは、1 日目から .NET を使用して私が拾ってきたヒントのほんの一部です。これらのテクニックにより、何度も私のベーコンが救われました。あなたのマイレージは異なる場合があります。

于 2008-10-08T16:33:12.360 に答える
65

優れたコードCodeProject の記事がここにあります。以下にいくつかのハイライトを示します。

  • 最悪の事態に備える*
  • 早めにチェック
  • 外部データを信用しない
  • 信頼できる唯一のデバイスは、ビデオ、マウス、およびキーボードです。
  • 書き込みも失敗する可能性があります
  • コードを安全に
  • new Exception() をスローしない
  • メッセージ フィールドに重要な例外情報を入力しないでください
  • スレッドごとに 1 つのキャッチ (Exception ex) を入れます
  • キャッチされた一般的な例外は公開する必要があります
  • 例外をログに記録します。ToString(); Exception.Message だけをログに記録しないでください。
  • スレッドごとに複数回 (例外) をキャッチしない
  • 例外を飲み込むな
  • クリーンアップ コードは、finally ブロックに配置する必要があります
  • どこでも「使用」を使用する
  • エラー状態で特別な値を返さない
  • リソースの不在を示すために例外を使用しないでください
  • メソッドから情報を返す手段として例外処理を使用しないでください
  • 無視してはならないエラーには例外を使用する
  • 例外を再スローするときにスタック トレースをクリアしない
  • セマンティック値を追加せずに例外を変更しないようにする
  • 例外は [Serializable] とマークする必要があります
  • 疑わしい場合は、アサートせず、例外をスローします
  • 各例外クラスには、少なくとも 3 つの元のコンストラクターが必要です。
  • AppDomain.UnhandledException イベントを使用する場合は注意してください
  • 車輪を再発明しないでください
  • 非構造化エラー処理 (VB.Net) を使用しないでください。
于 2008-10-08T16:30:46.110 に答える
16

Windows フォームには独自の例外処理メカニズムがあることに注意してください。フォーム内のボタンがクリックされ、そのハンドラーがハンドラーでキャッチされない例外をスローした場合、Windows フォームは独自の未処理の例外ダイアログを表示します。

Unhandled Exception Dialog が表示されないようにし、ロギングや独自のエラー ダイアログを提供するためにそのような例外をキャッチするには、Main() メソッドで Application.Run() を呼び出す前に Application.ThreadException イベントにアタッチします。

于 2009-04-24T20:08:32.980 に答える
15

これまでにここに投稿されたアドバイスはすべて良いものであり、注意する価値があります。

私が拡張したいことの1つは、「スローされる可能性のある例外の処理は、ディスク上のファイルが存在するかどうかなどの先制テストと比較して、パフォーマンスに影響を与えますか?」という質問です。

経験則では、「try/catchブロックは高価です」です。それは実際には真実ではありません。試してみるのは高くありません。これは、システムがExceptionオブジェクトを作成し、スタックトレースをロードする必要があるという問題であり、コストがかかります。例外が例外であり、コードをtry/catchブロックでラップしてもまったく問題がない場合が多くあります。

たとえば、辞書にデータを入力する場合、次のようになります。

try
{
   dict.Add(key, value);
}
catch(KeyException)
{
}

多くの場合、これを行うよりも高速です。

if (!dict.ContainsKey(key))
{
   dict.Add(key, value);
}

重複するキーを追加する場合にのみ例外がスローされるため、追加するすべてのアイテムに対して。(LINQ集約クエリはこれを行います。)

あなたが与えた例では、私はほとんど考えずにtry/catchを使用します。まず、チェックしたときにファイルが存在するからといって、開いたときにファイルが存在するという意味ではないので、とにかく例外を実際に処理する必要があります。

第二に、そしてもっと重要なことは、あなたのa)プロセスが何千ものファイルを開いていて、b)開いているファイルが存在しない確率が低くない限り、例外を作成することによるパフォーマンスへの影響はあなたのものではないということです。気付くつもりです。一般的に、プログラムがファイルを開こうとしているときは、1つのファイルだけを開こうとしています。これは、より安全なコードを書く方が、可能な限り最速のコードを書くよりも確実に優れている場合です。

于 2008-10-08T18:34:54.020 に答える
9

ここに私が従ういくつかのガイドラインがあります

  1. Fail-Fast: これは、より例外を生成するガイドラインです。作成するすべての仮定と、関数に取得するすべてのパラメーターについて、正しいデータで開始していること、および仮定が正しいことを確認するためにチェックを行います。作っているのは正しいです。典型的なチェックには、引数が null でない、引数が期待される範囲内にある、などが含まれます。

  2. 再スロー時にスタック トレースを保持 - これは単に、再スロー時に throw new Exception() の代わりに throw を使用することを意味します。または、さらに情報を追加できると思われる場合は、元の例外を内部例外としてラップします。ただし、ログに記録するためだけに例外をキャッチする場合は、必ず throw を使用してください。

  3. 処理できない例外をキャッチしないでください。OutOfMemoryException のようなものについて心配する必要はありません。

  4. グローバル例外ハンドラーをフックし、できるだけ多くの情報をログに記録してください。winforms の場合、appdomain とスレッド未処理の例外イベントの両方をフックします。

  5. パフォーマンスは、コードを分析し、パフォーマンスのボトルネックを引き起こしていることがわかった場合にのみ考慮する必要があります。デフォルトでは、読みやすさとデザインを最適化します。したがって、ファイルの存在チェックに関する最初の質問については、依存していると思います。ファイルが存在しないことについて何かできる場合は、そうでなければそのチェックを行います。そこにいないと、要点がわかりません。

  6. 空の catch ブロックが必要な場合は確かにあります。そうでないという人は、いくつかのリリースで進化したコードベースで作業したことがないと思います。ただし、それらが本当に必要かどうかを確認するために、コメントを付けてレビューする必要があります。最も典型的な例は、ParseInt() を使用する代わりに、try/catch を使用して文字列を整数に変換する開発者です。

  7. コードの呼び出し元がエラー条件を処理できると予想される場合は、予期しない状況の詳細を示すカスタム例外を作成し、関連情報を提供します。それ以外の場合は、可能な限り組み込みの例外タイプに固執してください。

于 2008-10-08T16:36:19.093 に答える
4

固執しようとしてきた黄金律は、例外を可能な限りソースの近くで処理することです。

例外を再スローして追加しようとする必要がある場合、FileNotFoundException を再スローしてもあまり役に立ちませんが、ConfigurationFileNotFoundException をスローすると、チェーンのどこかでキャプチャして処理できるようになります。

私が従おうとしているもう 1 つの規則は、プログラム フローの形式として try/catch を使用しないことです。そのため、ファイル/接続を検証し、オブジェクトが開始されていることなどを確認してから使用します。Try/catch は、制御できない例外に対して行う必要があります。

空の catch ブロックに関しては、例外を生成したコードで重要なことを行っている場合は、少なくとも例外を再スローする必要があります。実行されていない例外をスローしたコードの結果がない場合、なぜ最初にそれを書いたのですか。

于 2008-10-08T16:38:33.707 に答える
4

私は、自分の特定のコンテキストで処理が意味するものは何でも、処理するつもりのないものをキャッチしないという哲学が好きです。

次のようなコードを見ると嫌です。

try
{
   // some stuff is done here
}
catch
{
}

私はこれを時々見てきましたが、誰かが例外を「食べる」ときに問題を見つけるのは非常に困難です。私が持っていた同僚はこれを行っており、それは問題の絶え間ない流れに貢献する傾向があります.

特定のクラスが例外に応答して実行する必要がある場合は再スローしますが、問題が発生したメソッドを呼び出すために問題をバブルアウトする必要があります。

コードはプロアクティブに作成する必要があり、例外は条件のテストを避けるためではなく、例外的な状況に対応する必要があると思います。

于 2008-10-08T16:24:19.460 に答える
4

私はちょうど外出中ですが、例外処理をどこで使用するかについて簡単に説明します。私が戻ってきたら、あなたの他の点に対処しようとします:)

  1. すべての既知のエラー条件を明示的にチェック*
  2. すべてのケースを処理できたかどうかわからない場合は、コードの周りに try/catch を追加します
  3. 呼び出している .NET インターフェイスが例外をスローする場合は、コードの周りに try/catch を追加します。
  4. 複雑さのしきい値を超える場合は、コードの周りに try/catch を追加します
  5. サニティ チェックの場合は、コードの周りに try/catch を追加します。これは、これが発生する必要はないと主張しています。
  6. 原則として、リターン コードの代わりに例外を使用しません。これは .NET では問題ありませんが、私には問題ありません。ただし、このルールには例外があります (hehe)。これは、作業しているアプリケーションのアーキテクチャにも依存します。

*常識の範囲内。宇宙線がデータに衝突して数ビットが反転するかどうかを確認する必要はありません。「合理的」とは何かを理解することは、エンジニアにとって後天的なスキルです。数値化するのは難しいですが、直感的に理解するのは簡単です。つまり、特定のインスタンスで try/catch を使用する理由を簡単に説明できますが、同じ知識を別のインスタンスに吹き込むのは難しいです。

私は、例外ベースのアーキテクチャを大幅に回避する傾向があります。try/catch 自体にはパフォーマンス ヒットはありません。例外がスローされたときにヒットが発生し、何かがそれを処理する前に、コードがコール スタックのいくつかのレベルをたどる必要がある場合があります。

于 2008-10-08T16:25:22.907 に答える
2

例外は高価ですが必要です。すべてを try catch でラップする必要はありませんが、最終的に例外が常にキャッチされるようにする必要があります。その多くは、デザインに依存します。

例外を発生させても同様にうまくいく場合は、再スローしないでください。エラーを見過ごさないでください。

例:

void Main()
{
  try {
    DoStuff();
  }
  catch(Exception ex) {
    LogStuff(ex.ToString());
  }

void DoStuff() {
... Stuff ...
}

DoStuff がうまくいかない場合は、いずれにせよ保釈する必要があります。例外がメインにスローされ、ex のスタック トレースに一連のイベントが表示されます。

于 2008-10-08T16:22:38.857 に答える
1

次のルールに深く同意します。

  • エラーを見過ごさないでください。

その理由は次のとおりです。

  • 最初にコードを書き留めたときは、ほとんどの場合、サードパーティ コード、.NET FCL ライブラリ、または同僚の最新の貢献について十分な知識を持っていないでしょう。実際には、すべての例外の可能性を十分に理解するまで、コードを書くことを拒否することはできません。そう
  • 未知のものから身を守りたいという理由だけで、try / catch(Exception ex)を使用していることに常に気づきます。お気づきのように、OutOfMemoryExceptionなどのより具体的な例外ではなく、例外をキャッチします。そして、常に例外を作成しますForceAssert.AlwaysAssert(false, ex.ToString() ); によって私 (または QA) にポップアップ表示されます。

ForceAssert.AlwaysAssert は、DEBUG/TRACE マクロが定義されているかどうかに関係なく、Trace.Assert の私の個人的な方法です。

おそらく開発サイクル: 醜い Assert ダイアログに気付いた、または他の誰かがそれについて私に文句を言ったので、コードに戻って例外を発生させる理由を見つけ、それを処理する方法を決定します。

これにより、短時間でMYコードを書き留めることができ、未知のドメインから身を守ることができましたが、異常が発生した場合は常に通知されるため、システムはより安全になりました。

開発者は自分のコードのすべての詳細を知っている必要があるため、多くの人が私に同意しないことを知っています。率直に言って、私は昔は純粋主義者でもありました。しかし、最近では、上記のポリシーの方が実際的であることがわかりました。

WinForms コードの場合、私が常に従う黄金律は次のとおりです。

  • イベント ハンドラ コードを常に try/catch(Exception) します

これにより、UI が常に使用できるようになります。

パフォーマンス ヒットの場合、パフォーマンス ペナルティはコードが catch に達したときにのみ発生し、実際の例外が発生せずに try コードを実行しても大きな影響はありません。

例外はほとんど発生しないはずです。それ以外の場合は例外ではありません。

于 2009-03-15T15:21:17.667 に答える
1

私の経験では、例外を作成することがわかっている場合は、例外をキャッチするのに適していると考えています。たとえば、Web アプリケーションで Response.Redirect を実行している場合、System.ThreadAbortException が発生することはわかっています。意図的なものなので、特定のタイプをキャッチして飲み込むだけです。

try
{
/*Doing stuff that may cause an exception*/
Response.Redirect("http:\\www.somewhereelse.com");
}
catch (ThreadAbortException tex){/*Ignore*/}
catch (Exception ex){/*HandleException*/}
于 2009-02-05T21:27:57.637 に答える
1

いつ例外を再スローする必要がありますか?

どこでも、ただしエンド ユーザー メソッド... ボタン クリック ハンドラーなど

ある種の中心的なエラー処理メカニズムを持つべきですか?

私はログファイルを書きます... WinFormアプリにとってはかなり簡単です

スローされる可能性のある例外を処理すると、ディスク上のファイルが存在するかどうかなどを先制的にテストする場合と比較して、パフォーマンスが低下しますか?

これについてはよくわかりませんが、例外をスローするのは良い習慣だと思います...つまり、ファイルが存在するかどうか、ファイルがスローされないかどうかを尋ねることができます FileNotFoundException

すべての実行可能コードを try-catch-finally ブロックで囲む必要がありますか?

うん

空の catch ブロックが許容される場合はありますか?

はい、日付を表示したいとしましょう。ただし、その日付がどのように格納されたのか (dd/mm/yyyy、mm/dd/yyyy など) はわかりません。解析を試みますが、失敗した場合は続行してください.. . それがあなたに関係のないことであれば... はい、あります。

于 2008-10-08T16:25:27.227 に答える
1

私がすぐに学んだことの 1 つは、プログラムのフロー外のもの(つまり、ファイル システム、データベース呼び出し、ユーザー入力) とやり取りするコードのすべてのチャンクを、try-catch ブロックで囲むことです。Try-catch はパフォーマンスに影響を与える可能性がありますが、通常、コード内のこれらの場所では目立たず、安全性が確保されます。ユーザーが実際には「正しくない」ことを行う可能性がある場所で空のキャッチブロックを使用しましたが、例外がスローされる可能性があります...ユーザーが灰色のプレースホルダーをダブルクリックした場合、頭に浮かぶ例はGridViewにあります左上のセルは CellDoubleClick イベントを発生させますが、セルは行に属していません。その場合、あなたはいけません

本当にメッセージを投稿する必要がありますが、それをキャッチしないと、未処理の例外エラーがユーザーにスローされます。

于 2008-10-08T16:26:04.420 に答える
1

例外を再スローするとき、キーワード throw by itself 。これにより、キャッチされた例外がスローされますが、スタック トレースを使用して、それがどこから来たかを確認できます。

Try
{
int a = 10 / 0;
}
catch(exception e){
//error logging
throw;
}

これを行うと、スタック トレースが catch ステートメントで終了します。(これは避けてください)

catch(Exception e)
// logging
throw e;
}
于 2008-10-08T16:38:02.243 に答える