Windowsフォームアプリケーションでの例外処理のベストプラクティス


118

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

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

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

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

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

回答:


79

もう少し...

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

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

例外を「飲み込む」ことは絶対に避けてください。例外がスローされている場合は、確実に確実にスローされます。これはほとんど当てはまりません。(もしそうなら、特定の例外クラスだけを飲み込んでいることを確認してください- 飲み込まないくださいSystem.Exception。)

(アプリで使用される)ライブラリを構築するときは、例外を飲み込まないでください。また、例外が発生することを恐れないでください。追加する何か有用なものがない限り、再スローしないでください。これまで(C#では)これを行わないでください。

throw ex;

コールスタックを消去します。再スローする必要がある場合(エンタープライズライブラリの例外処理ブロックを使用する場合など、必要になる場合があります)、以下を使用します。

throw;

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

.NETの例外処理は、科学よりも芸術です。ここで共有するお気に入りがあります。これらは、1日目以降に.NETを使用して選択したヒントのほんの一部に過ぎません。ベーコンを何度も節約したテクニック。あなたのマイレージは異なる場合があります。


1
例外が発生すると、呼び出し元はどのようにして例外が操作の失敗を示しているかを知っているはずですが、システムは基本的に問題ありません(たとえば、ユーザーが破損したドキュメントファイルを開こうとした場合、ファイルは読み込まれませんが、すべてがそれ以外の場合は問題ありません)、またはそれがCPUが燃えていることを示しており、できるだけ早く出口に向かう必要がありますか?ArgumentExceptionのようなものは、スローされた状況に応じて、どちらかを示す可能性があります。
スーパーキャット

2
@supercat ApplicationExceptionアプリケーションが合理的に区別して処理できる必要のある障害の場合の特定のサブクラスを記述することによって。
Matt Enright、2011

@マット・エンライト:確かに自分自身の例外をキャッチすることは可能ですが、例外をスローするモジュールが、障害によって暗示されるもの以外の状態の破損を示しているかどうかを示す規則にリモートで似ているものは知りません。理想的には、SuperDocument.CreateFromFile()のようなメソッドが成功するか、CleanFailure例外をスローするか、SomethingReallyBadHappenedExceptionをスローします。残念ながら、例外をスローする可能性のあるすべてのものを独自のcatchブロックでラップしない限り、InvalidOperationException ...
supercat

@Matt Enright:... CleanFailureExceptionまたはSomethingReallyBadHappenedExceptionでラップする必要があります。そして、すべてを個別のtry-catchブロックでラップすると、そもそも例外を持つという目的全体が無効になります。
スーパーキャット2011

2
それは、不正確な例外タイプで失敗のモードを隠すための、貧弱なライブラリ設計だと思います。実際の意味がIOExceptionまたはInvalidDataExceptionの場合は、アプリケーションがそれぞれのケースに異なる応答をする必要があるため、FileNotFoundExceptionをスローしないでください。StackOverflowExceptionやOutOfMemoryExceptionのようなものは、アプリケーションで適切に処理できないため、正常に動作するアプリケーションには一元化された「最後の手段」のハンドラーが配置されているため、それらをバブルさせてください。
マット・エンライト、2011

63

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

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

3
この点についてもう少し詳しく説明してもらえますか?「リソースがないことを示すために例外を使用しないでください」私はその背後にある理由を理解しているのかよくわかりません。また、単なる注釈です。この答えは、「なぜ」を説明するものではありません。私は、これは5歳知っているが、それはまだバグ私をビット
レミ

参照記事へのリンクは期限切れです。Code Projectは、記事がここに移動し可能性があることを示唆しています
DavidRR 2016

15

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

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


予想通り、この提案のおかげで、私は遅すぎるこのことを学んだ、私はちょうどlinqpadでそれを検証し、働き、デリゲートがあります。void Form1_UIThreadException(オブジェクト送信者、ThreadExceptionEventArgsトン)このトピックについてのもう一つの良いソースがある richnewman.wordpress.com/2007/ 04/08 /… 未処理の例外処理全体:AppDomain.UnhandledExceptionイベントは、非メインUIスレッドからスローされた未処理の例外用です。
zhaorufei 2015年

14

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

私が拡張したいことの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つのファイルを開こうとしているだけです。これは、より安全なコードを書く方が、可能な限り最速のコードを書くよりも確実に優れているケースです。


3
dictの場合、あなたはただ行うことができます:dict[key] = valueこれはより速くなくても同じくらい速くなければなりません。
nawfal

9

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

  1. Fail-Fast:これはより多くの例外生成ガイドラインです。作成するすべての仮定と、関数に入れるすべてのパラメーターについて、適切なデータから始めて、作りは正しいです。一般的なチェックには、引数がnullではない、期待される範囲の引数などがあります。

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

  3. 処理できない例外はキャッチしないでください。OutOfMemoryExceptionのようなことは心配しないでください。例外が発生すると、とにかく多くのことを行うことができなくなります。

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

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

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

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


appdomainとスレッドの両方の未処理の例外ハンドラーをフックする用途は何ですか?Appdomain.UnhandledExceptionを使用するだけの場合、すべてをキャッチする最も一般的なものではありませんか?
Quagmire

Application.ThreadException「スレッドの未処理の例外」イベントを参照したときに、イベントを処理することを意味しましたか?
ジェフB

4

私は、特定の状況での処理方法が何であれ、処理するつもりのないものを捕まえないという哲学が好きです。

次のようなコードを見ると、私はそれが嫌いです。

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

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

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

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


@Kiquenet申し訳ありませんが、少し不明瞭でした。私が意味したこと:そのような空のキャッチを行わず、代わりにハンドラーをDispatcher.UnhandledExceptionに追加し、少なくともログを追加して、パンくずなしでは食べられないようにします:)しかし、サイレントコンポーネントの要件がない限り、常に例外を含める必要がありますあなたのデザインに。それらをスローする必要があるときにそれらをスローし、インターフェイス/ API /クラスの明確な契約を作成します。
LuckyLikey

4

間もなく終了しますが、例外処理を使用する場所について簡単に説明します。私が戻ったとき、私はあなたの他のポイントに対処しようとします:)

  1. すべての既知のエラー状態を明示的にチェックします*
  2. すべてのケースを処理できたかどうか不明な場合は、コードの前後にtry / catchを追加してください
  3. 呼び出している.NETインターフェイスが例外をスローする場合は、コードの前後にtry / catchを追加します
  4. コードが複雑さのしきい値を超えている場合は、コードの周りにtry / catchを追加します
  5. 健全性チェックの場合は、コードの前後にtry / catchを追加します。これは、絶対に発生しないことを表明するものです
  6. 原則として、私は例外を戻りコードの代わりとして使用しません。これは.NETには問題ありませんが、私には問題ありません。このルールには例外がありますが、それはあなたが取り組んでいるアプリケーションのアーキテクチャにも依存します。

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

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


4

固執しようとしているゴールデンルールは、例外をできるだけソースの近くで処理することです。

例外を再度スローする必要がある場合、例外を追加しようとしても、FileNotFoundExceptionを再度スローしても効果はありませんが、ConfigurationFileNotFoundExceptionをスローすると、チェーンのどこかで例外をキャプチャして操作できます。

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

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


この「黄金律」はせいぜい恣意的なものです。
エヴァンハーパー

3

ThreadExceptionイベントをトラップできます。

  1. ソリューションエクスプローラーでWindowsアプリケーションプロジェクトを選択します。

  2. 生成されたProgram.csファイルをダブルクリックして開きます。

  3. コードファイルの先頭に次のコード行を追加します。

    using System.Threading;
  4. Main()メソッドで、メソッドの最初の行として次を追加します。

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
  5. Main()メソッドの下に以下を追加します。

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        // Do logging or whatever here
        Application.Exit();
    }
  6. イベントハンドラ内で未処理の例外を処理するコードを追加します。アプリケーションの他の場所で処理されない例外は、上記のコードによって処理されます。最も一般的には、このコードはエラーをログに記録し、ユーザーにメッセージを表示します。

refrence:https ://blogs.msmvps.com/deborahk/global-exception-handler-winforms/


@Kiquenet申し訳ありません、VBについては知りません
Omid-RH

2

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

例外を発生させても同じように動作する場合は、再スローしないでください。エラーに気づかれないようにしてください。

例:

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

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

DoStuffがうまくいかない場合は、とにかくそれを保釈したいと思うでしょう。例外がメインにスローされ、exのスタックトレースに一連のイベントが表示されます。


1

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

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

なんらかの中心的なエラー処理メカニズムを用意する必要がありますか?

ログファイルを作成します... WinFormアプリではかなり簡単です

スローされる可能性のある例外を処理すると、ディスク上のファイルが存在するかどうかなどの事前のテストと比較して、パフォーマンスに影響がありますか?

私はこれについてはわかりませんが、例外を処理することは良い習慣だと思います...つまり、ファイルが存在するかどうか、またFileNotFoundExceptionをスローしないかどうかを確認できます

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

うん

空のcatchブロックが受け入れられる場合がありますか?

はい、日付を表示したいとしますが、その日付がどのように格納されているか(dd / mm / yyyy、mm / dd / yyyyなど)がわからない場合は、tpで解析してみますが、失敗した場合はそのまま続行します。 。それがあなたと無関係の場合...私はそう言うでしょう、


1

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

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


1

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

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

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

catch(Exception e)
// logging
throw e;
}

これは、最新バージョンの.NET Frameworkと.NET Coreにも適用されますか?
LuckyLikey

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*/}

1

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

  • エラーに気づかれないようにしてください。

その理由は:

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

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

開発サイクルは多分:醜いアサートダイアログまたは他の誰かがそれについて不平を言っているのに気付いたので、コードに戻って、例外を発生させる理由を理解し、その処理方法を決定します。

このようにして、短時間でMYコードを書き留め、未知のドメインから保護することができますが、異常な事態が発生した場合は常に気づかれ、このようにしてシステムはより安全になりました。

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

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

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

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

パフォーマンスヒットの場合、パフォーマンスが低下するのは、コードがcatchに到達したときだけです。実際の例外を発生させずにtryコードを実行しても、大きな影響はありません。

例外はほとんど起こりませんが、そうでなければ例外ではありません。


-1

ユーザーについて考える必要があります。アプリケーションのクラッシュは最後ですユーザーが望むもの。したがって、失敗する可能性のある操作には、UIレベルでtry catchブロックが必要です。すべてのメソッドでtry catchを使用する必要はありませんが、ユーザーが何かを行うたびに、一般的な例外を処理できる必要があります。これにより、最初のケースで例外を防ぐためにすべてをチェックする必要がなくなるわけではありませんが、バグのない複雑なアプリケーションはなく、OSは予期しない問題を簡単に追加できるため、予期しない問題を予測し、ユーザーが問題を使用するかどうかを確認する必要があります。アプリがクラッシュするため、データの損失はありません。アプリをクラッシュさせる必要はありません。例外をキャッチすると、不確定な状態になることはなく、ユーザーは常にクラッシュによって不便になります。例外が最上位であっても クラッシュしないということは、ユーザーが例外をすばやく再現したり、少なくともエラーメッセージを記録したりできるため、問題を解決するのに非常に役立ちます。確かに、単純なエラーメッセージが表示されてから、Windowsのエラーダイアログなどだけが表示されるだけではありません。

だからといって、決してうぬぼれてはならず、アプリにバグがないと思ってはいけません。保証はありません。そして、適切なコードについていくつかのtry catchブロックをラップし、エラーメッセージを表示したり、エラーをログに記録したりするのは非常に小さな労力です。

ユーザーとして、私は眉やオフィスアプリ、または何かがクラッシュするたびに深刻な腹を立てます。例外が高すぎてアプリが続行できない場合は、単にクラッシュするだけでなく、そのメッセージを表示してユーザーに何をすべきか(再起動、OS設定の修正、バグの報告など)を伝える方が適切です。


あなたは怒りのように答えを書くのではなく、より具体的にしてあなたの主張を証明しようとすることができますか?また、回答方法もご覧ください
LuckyLikey
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.