web-dev-qa-db-ja.com

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

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

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

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

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

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

117
Jon Artus

さらにいくつかのビット...

例外処理ポリシーを集中管理する必要があります。これは、try/catchでMain()をラップするだけで、ユーザーに適切なエラーメッセージを表示して高速で失敗します。これは「最後の手段」の例外ハンドラです。

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

例外を絶対に「飲み込む」ことはできません。ただし、最もよく文書化されている場合を除き、例外が投げられることは絶対に確実に受け入れられると確信しています。これはほとんどありません。 (もしそうなら、specific例外クラスのみを飲み込むようにしてください-ever飲み込まないSystem.Exception。)

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

throw ex;

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

throw;

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

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

79
John Rudy

素晴らしいコードがあります CodeProjectの記事はこちら 。ハイライトは次のとおりです。

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

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

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

15
user95680

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

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

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

たとえば、ディクショナリを作成する場合、次のようにします。

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

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

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

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

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

第二に、より重要なことは、a)プロセスが数千のファイルを開いており、b)開こうとしているファイルが存在しないという可能性がさほど低くない限り、例外を作成することによるパフォーマンスへの影響はあなたのものではない再気づくでしょう。一般的に言えば、プログラムがファイルを開こうとしているときは、1つのファイルを開こうとしているだけです。これは、より高速なコードを書くよりも安全なコードを書く方がほぼ間違いなく優れているケースです。

14
Robert Rossney

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

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

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

  3. 処理できない例外をキャッチしないでください。そのため、OutOfMemoryExceptionなどが発生しても心配する必要はありません。

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

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

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

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

9
Sijin

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

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

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

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

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

コードは積極的に書かれるべきであり、例外は例外的な状況のためのものであり、条件のテストを避けるためのものではないと思います。

4
itsmatt

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

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

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

私は、非常に例外に基づいたアーキテクチャから遠ざかる傾向があります。 try/catchにはパフォーマンスヒットはありません。例外がスローされるとヒットが発生し、コードは何かを処理する前にコールスタックのいくつかのレベルを上る必要があります。

4

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

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

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

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

4
Dan B

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

例外を発生させても同様に機能する場合は、再スローしないでください。エラーを気付かずに渡さないでください。

例:

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

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

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

2
Echostorm

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

  • エラーを気付かずに渡さないでください。

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

  • 最初にコードを書き留めるときには、サードパーティコード、.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が常に使用可能になります。

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

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

1
zhaorufei

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

どこでも、エンドユーザーメソッド...ボタンクリックハンドラーのような

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

ログファイルを作成します... WinFormアプリの場合は非常に簡単です

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

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

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

うん

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

はい、日付を表示したいとしますが、その日付がどのように保存されたのかわかりません(dd/mm/yyyy、mm/dd/yyyyなど)。 。それがあなたに無関係なら...私はイエスと言うでしょう、

1
sebagomez

私が非常に早く学んだことは、絶対にプログラムのフローの外側でanythingとやり取りするコードの塊(つまり、ファイルシステム、データベース呼び出し、ユーザー入力)を囲むことでした。 try-catchブロックを使用します。 Try-catchはパフォーマンスに影響を与える可能性がありますが、通常、コード内のこれらの場所では目立たず、安全に費用がかかります。

ユーザーが実際に「正しくない」ことを行う可能性のある場所で空のcatch-blockを使用しましたが、例外をスローする可能性があります...頭に浮かぶ例はGridViewにありますユーザーが左上にある灰色のプレースホルダーセルをダブルクリックすると、CellDoubleClickイベントが発生しますが、セルは行に属していません。その場合、あなたはdont本当にメッセージを投稿する必要がありますが、あなたがそれを捕まえなければ、未処理の例外エラーをユーザーにスローします。

1
BKimmel

私の経験では、例外を作成することがわかっているときに例外をキャッチするのに適していると考えています。 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
Jeff Keslinke

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

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

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

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

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/

1
Omid-RH