web-dev-qa-db-ja.com

例外処理にtry catchを使用する方法がベストプラクティスです。

私の同僚のコードをシニア開発者であると主張する誰かからのものでさえ維持している間、私はしばしば以下のコードを見る:

try
{
  //do something
}
catch
{
  //Do nothing
}

あるいはtry catchブロックのようにログファイルにログ情報を書き込むこともあります。

try
{
  //do some work
}
catch(Exception exception)
{
   WriteException2LogFile(exception);
}

私は彼らがしたことがベストプラクティスであるかどうか疑問に思いますか?私の考えではユーザーはシステムで何が起こるかを知っているべきだから、それは私を混乱させます。

何かアドバイスをください。

190
Toan Nguyen

私の例外処理戦略は以下のとおりです。

  • Application.ThreadException eventにフックしてすべての未処理の例外をキャッチするには、次のように決めます。

    • UIアプリケーションの場合:謝罪メッセージを表示してユーザーに表示する(winforms)
    • サービスまたはコンソールアプリケーションの場合:ファイルに記録します(サービスまたはコンソール)。

それから私は常に外部で実行されるコードのすべての部分try/catchで囲みます。

  • Winformsインフラストラクチャによって発生したすべてのイベント(Load、Click、SelectedChanged ...)
  • サードパーティコンポーネントによって発生したすべてのイベント

それから私は 'try/catch'で囲みます

  • すべてのI が知っている操作はいつもうまくいくとは限らない (IO操作、ゼロ除算の可能性がある計算...)。そのような場合、私は本当に起こったことを追跡するために新しいApplicationException("custom message", innerException)を投げます。

さらに、例外を正しくソートするに最善を尽くします。例外があります。

  • すぐにユーザーに見せる必要があります
  • カスケードの問題を回避するために偶然に物事をまとめるために追加の処理を必要とします(例:finallyの塗りつぶしの間にTreeViewセクションに.EndUpdateを置く)
  • ユーザーは気にしませんが、何が起こったのかを知ることが重要です。だから私はいつもそれらをログに記録します。

    • イベントログ内
    • またはディスク上の.logファイルに

アプリケーションのトップレベルのエラーハンドラで例外を処理するための静的メソッドを設計するを実行することをお勧めします。

私は自分自身もやろうとすることを強制します:

  • すべての例外は最上位レベルまで表示されますを覚えておいてください。例外ハンドラを至る所に置く必要はありません。
  • 再利用可能または深く呼び出された関数は、例外を表示または記録する必要はありません。それらは自動的にバブルアップされるか、または私の例外ハンドラでいくつかのカスタムメッセージで再スローされます。

だから最後に:

悪い:

// DON'T DO THIS, ITS BAD
try
{
    ...
}
catch 
{
   // only air...
}

無用:

// DONT'T DO THIS, ITS USELESS
try
{
    ...
}
catch(Exception ex)
{
    throw ex;
}

最後にキャッチせずに試してみることは完全に有効です。

try
{
    listView1.BeginUpdate();

    // If an exception occurs in the following code, then the finally will be executed
    // and the exception will be thrown
    ...
}
finally
{
    // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT
    listView1.EndUpdate();
}

トップレベルで私がしていること:

// i.e When the user clicks on a button
try
{
    ...
}
catch(Exception ex)
{
    ex.Log(); // Log exception

    -- OR --

    ex.Log().Display(); // Log exception, then show it to the user with apologies...
}

関数と呼ばれるもので私がしていること:

// Calculation module
try
{
    ...
}
catch(Exception ex)
{
    // Add useful information to the exception
    throw new ApplicationException("Something wrong happened in the calculation module :", ex);
}

// IO module
try
{
    ...
}
catch(Exception ex)
{
    throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex);
}

例外処理(カスタム例外)には多くのことを行う必要がありますが、私が念頭に置いているそれらの規則は、私が行う単純なアプリケーションには十分です。

これは、キャッチされた例外を快適な方法で処理するための拡張メソッドの例です。それらは一緒にチェーンできる方法で実装されています、そしてあなた自身のキャッチされた例外処理を追加することは非常に簡単です。

// Usage:

try
{
    // boom
}
catch(Exception ex)
{
    // Only log exception
    ex.Log();

    -- OR --

    // Only display exception
    ex.Display();

    -- OR --

    // Log, then display exception
    ex.Log().Display();

    -- OR --

    // Add some user-friendly message to an exception
    new ApplicationException("Unable to calculate !", ex).Log().Display();
}

// Extension methods

internal static Exception Log(this Exception ex)
{
    File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n");
    return ex;
}

internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error)
{
    MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img);
    return ex;
}
290
Larry

ベストプラクティスは、例外処理 が問題を隠さないようにすることです 。これはtry-catchブロックが非常にまれであるべきであることを意味します。

try-catchを使用するのが合理的な状況は3つあります。

  1. known の例外はできる限り低位で扱うようにしてください。ただし、例外が発生すると予想される場合は、通常、まず例外をテストすることをお勧めします。たとえば、解析、フォーマット、および算術例外は、特定のtry-catchよりも、最初にロジックチェックで処理する方がほぼ常に適切です。

  2. 例外に対して何か(例えばロギングやトランザクションのロールバックなど)する必要がある場合は、その例外を再度スローします。

  3. unknown exceptionsは、可能な限り高位で扱うようにしてください。 - only 例外を消費し、それを再スローしないコードは、UIまたはパブリックAPIである必要があります。

リモートAPIに接続しているとします。ここでは特定のエラーが予想されることがわかっています(その場合は対処する必要があります)。これがケース1です。

try 
{
    remoteApi.Connect()
}
catch(ApiConnectionSecurityException ex) 
{
    // User's security details have expired
    return false;
}

return true;

予期されていないため、他の例外はキャッチされません。

データベースに何かを保存しようとしているとしましょう。失敗した場合はロールバックする必要があるため、ケース2があります。

try
{
    DBConnection.Save();
}
catch
{
    // Roll back the DB changes so they aren't corrupted on ANY exception
    DBConnection.Rollback();

    // Re-throw the exception, it's critical that the user knows that it failed to save
    throw;
}

例外を再スローしていることに注意してください - 上のコードはまだ何かが失敗したことを知る必要があります。

最後に私たちはUIを持っています - ここで私たちは完全に未処理の例外を持ちたくありませんが、私たちはそれらを隠したくもありません。これがケース3の例です。

try
{
    // Do something
}
catch(Exception ex) 
{
    // Log exception for developers
    WriteException2LogFile(ex);

    // Display message to users
    DisplayWarningBox("An error has occurred, please contact support!");
}

ただし、ほとんどのAPIまたはUIフレームワークにはケース3の一般的な方法があります。たとえば、ASP.Netには例外の詳細を表示する黄色のエラー画面がありますが、実稼働環境ではより一般的なメッセージに置き換えることができます。多くのコードを節約するので、これらに従うのがベストプラクティスですが、エラーロギングと表示はハードコーディングされるのではなく設定の決定であるべきです。

これはすべてケース1(既知の例外)とケース3(1回限りのUI処理)の両方がより良いパターンを持っていることを意味します(予期されるエラーや手動エラー処理を回避するためのUI)。

トランザクションスコープ (ブロック中にコミットされていないトランザクションをロールバックするusingブロック)は、開発者がベストプラクティスパターンを間違って取得することを難しくします。

たとえば、大規模なASP.Netアプリケーションがあるとします。エラーロギングは _ elmah _ で行うことができます。エラー表示はローカルで有益なYSoDで、本番環境ではNiceローカライズされたメッセージにすることができます。データベース接続はすべてトランザクションスコープとusingブロックを介して行われます。単一のtry-catchブロックは必要ありません。

TL; DR:ベストプラクティスは、実際にはtry-catchブロックをまったく使用しないことです。

57
Keith

例外は、ブロッキングエラーです。

まず第一に、ベストプラクティスはブロッキングエラーでない限り、あらゆる種類のエラーに対して例外をスローしないことです。

エラーが blocking の場合、例外をスローします。例外がすでにスローされると、例外は例外であるため、非表示にする必要はありません。ユーザーにそのことを知らせてください(UIでユーザーにとって有用なものに例外全体を再フォーマットする必要があります)。

ソフトウェア開発者としてのあなたの仕事は、例外的なケースを防ぐように努力することです。つまり、例外をミュートすることはできませんが、これらを避ける必要があります

たとえば、 integer 入力に無効な形式が含まれることがあることがわかっている場合は、int.TryParseの代わりにint.Parseを使用します。 「失敗した場合は、単に例外をスローする」と言うだけでなく、これを行うことができる多くのケースがあります。

例外を投げることは高価です。

結局、例外がスローされたら、例外がスローされた後にログに書き込むのではなく、ベストプラクティスの1つは first-chance exception handler でキャッチすることです。例えば:

  • ASP.NET:Global.asax Application_Error
  • その他:AppDomain.FirstChanceExceptionイベント

私のスタンスは、例外を別の例外に変換する場合や、非常に、非常に、非常に、非常に特別な場合に「ミュート」したい場合に、ローカルのtry/catchesがより適しているということです。バグ全体を回避するためにミュートする必要がある無関係な例外をスローします)。

残りの場合:

  • 例外を避けるようにしてください。
  • これが不可能な場合:初回例外ハンドラー。
  • または、PostSharpアスペクト(AOP)を使用します。

コメントで@thewhiteambitに回答しています...

@thewhiteambitは言った:

例外は致命的なエラーではなく、例外です!時にはエラーではないこともありますが、致命的なエラーと見なすことは、例外とは何かを完全に誤って理解することです。

まず第一に、どのように例外がエラーにならないのでしょうか?

  • データベース接続なし=>例外。
  • 何らかのタイプ=>例外に解析するための無効な文字列形式
  • JSONを解析しようとしていますが、入力は実際にはJSONではありません=>例外
  • 引数nullオブジェクトが予期されていたとき=>例外
  • 一部のライブラリにはバグがあります=>予期しない例外がスローされます
  • ソケット接続があり、切断されます。次に、メッセージを送信しよう==例外
  • ...

例外がスローされた場合の1kのケースをリストできますが、結局、考えられるケースのいずれかはエラーになります。

例外は /エラーです。1日の終わりには診断情報を収集するオブジェクトであるためです。メッセージがあり、何か問題が発生すると発生します。

例外的なケースがない場合、誰も例外をスローしません。例外はブロッキングエラーである必要があります。一度スローされると、に入ろうとしない場合は、try/catchと例外を使用して制御フローを実装しますこれらは、アプリケーション/サービスが例外ケースに入った操作を停止することを意味します。

また、 fail-fast パラダイム Martin Fowlerが発行(およびJim Shoreが作成) を確認することをお勧めします。これは、少し前にこのドキュメントに到達する前でさえ、例外を処理する方法を常に理解していた方法です。

[...]致命的エラーとは、例外とは何かを完全に誤って理解していることです。

通常は例外 cut いくつかの操作フローであり、それらは人間が理解できるエラーに変換するために処理されます。したがって、例外は実際にはエラーのケースを処理し、アプリケーション/サービスの完全なクラッシュを回避し、何かが間違っていることをユーザー/消費者に通知するように動作するより良いパラダイムのようです。

@thewhiteambitの懸念に関するその他の回答

たとえば、データベース接続が欠落している場合、プログラムは例外的にローカルファイルへの書き込みを続行し、再び使用可能になったらデータベースに変更を送信できます。無効な文字列から数字へのキャストは、Parse( "1,5")へのデフォルトの英語の試みが失敗し、完全にドイツ語の解釈でもう一度試みるように、例外の言語ローカル解釈で再度解析を試みることができます。区切り文字としてポイントの代わりにコンマを使用するため、問題ありません。これらの例外はブロックすることさえできず、例外処理が必要なだけです。

  1. データベースにデータを保持せずにアプリがオフラインで動作する可能性がある場合は、[ exceptions を使用しないでください。try/catchを使用した制御フローの実装はアンチパターンと見なされます。 オフライン作業が可能なユースケースです。データベースにアクセスできるかどうかを確認するために制御フローを実装し、到達不能になるまで待たない

  2. parsing も期待されるケースです(not EXCEPTIONAL CASE)。これが予想される場合、制御フローの実行に例外を使用しないでください!。ユーザーからメタデータを取得して、ユーザーの文化を把握し、そのためにフォーマッターを使用します! 。NETは、この環境と他の環境もサポートします。アプリケーション/サービスのカルチャ固有の使用を期待する場合は、数値の書式設定を避ける必要があるため、例外があります

通常、未処理の例外はエラーになりますが、例外自体はcodeproject.com/Articles/15921/Not-All-Exceptions-Are-Errorsではありません

この記事は、著者の意見または見解にすぎません。

ウィキペディアはアーティクル作成者の意見でもあるため、ドグマとは言いませんが、何を確認してください exception 記事はいくつかの段落のどこかに記載されています:

[...]これらの例外を使用して、プログラムを続行するために発生する特定のエラーを処理することを、例外によるコーディングと呼びます。 このアンチパターンは、ソフトウェアのパフォーマンスと保守性を急速に低下させる可能性があります。

また、どこかに言っています:

誤った例外の使用

多くの場合、例外によるコーディングは、不正な例外の使用によるソフトウェアのさらなる問題につながる可能性があります。一意の問題に例外処理を使用することに加えて、例外が発生した後でもコードを実行することで、不正な例外の使用によりさらにこれが行われます。この貧弱なプログラミング方法は、多くのソフトウェア言語のgotoメソッドに似ていますが、ソフトウェアの問題が検出された後にのみ発生します。

正直なところ、ソフトウェアを開発することはできないと思います。ユースケースを真剣に考えないでください。知っているなら...

  • データベースをオフラインにできます...
  • 一部のファイルをロックできます...
  • 一部のフォーマットがサポートされていない可能性があります...
  • 一部のドメイン検証が失敗する可能性があります...
  • アプリはオフラインモードで動作するはずです...
  • どのようなユースケース ...

...その例外を使用しないsupport通常の制御フローを使用したこれらのユースケースです。

また、予期しないユースケースがカバーされていない場合、コードは高速で失敗します。これは、例外がスローされるであるためです。右、例外は例外ケースであるためです。

一方、最後に、例外的なケーススロー期待される例外をカバーする場合もありますが、制御フローを実装するためにスローしません。これは、ユースケースをサポートしていないか、特定の引数または環境データ/プロパティでコードが機能しないことを上位レイヤーに通知するためです。

32

私はこれが古い質問であることを知っていますが、ここでMSDNの記事に言及した人はいませんでした、そしてそれは実際にそれをクリアしたドキュメントでした、MSDNにはこれについて 非常に良いドキュメント があり、次の条件に該当します。

  • 例外がスローされる理由を十分に理解しており、FileNotFoundExceptionオブジェクトをキャッチしたときにユーザーに新しいファイル名の入力を求めるなど、特定の回復を実装できます。

  • 新しい、より具体的な例外を作成してスローできます。

int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch(System.IndexOutOfRangeException e)
    {
        throw new System.ArgumentOutOfRangeException(
            "Parameter index is out of range.");
    }
}
  • 追加の処理のために例外を渡す前に、例外を部分的に処理したい場合。次の例では、catchブロックを使用して、例外を再スローする前にエラーログにエントリを追加します。
    try
{
    // Try to access a resource.
}
catch (System.UnauthorizedAccessException e)
{
    // Call a custom error logging procedure.
    LogError(e);
    // Re-throw the error.
    throw;     
}

例外と例外処理 」セクション全体と、 例外のベストプラクティス を読むことをお勧めします。

5
Hamid Mosalla

あなたがコードで起こったことについてあなたのユーザを心配すべき唯一の時は問題を避けるために彼らがすることができるか、またはする必要がある何かがあるかどうかです。フォーム上のデータを変更できる場合は、問題を回避するためにボタンを押すかアプリケーション設定を変更してから通知してください。しかし、ユーザーが回避する能力がないという警告やエラーは、製品に対する信頼を失うだけです。

例外とログは開発者であり、エンドユーザーではありません。それぞれの例外をキャッチするときに行うべき正しいことを理解することは、単にいくつかの黄金律を適用することや、アプリケーション全体のセーフティネットに頼ることよりはるかに優れています。

無意識のコーディングは唯一の間違ったコーディングです。このような状況でできることがもっとあると感じているという事実は、良いコーディングに投資していることを示していますが、これらの状況で一般的な規則をスタンプしようとしないことを避けてください。あなたはそれから回復するためにすることができます。

5
ChrisCW

2つ目の方法(例外の種類を指定する方法)がより良い方法です。この利点は、この種の例外がコード内で発生する可能性があることを知っていることです。あなたはこの種の例外を処理しており、再開することができます。他の例外が発生した場合、それは何かが間違っていることを意味し、それはあなたがあなたのコードの中のバグを見つけるのを助けるでしょう。アプリケーションは最終的にはクラッシュしますが、見逃したもの(バグ)があることに気づくようになるでしょう。

1
fhnaseer

例外を除いて、私は次のことを試みます。

まず、ゼロ除算、IO操作などの特殊な例外をキャッチし、それに従ってコードを作成します。たとえば、ゼロによる除算は、ユーザに警告することができる値の素因に応じて(引数ではなく中間の計算でゼロによる除算に到達するという単純な計算機など)、またはその例外を静かに処理しますそれを処理し続けます。

それから私は残りの例外をキャッチしてそれらをログに記録しようとします。可能であればコードの実行を許可し、それ以外の場合はエラーが発生したことをユーザーに警告し、エラー報告をメールで送信するように依頼します。

コードでは、次のようになります。

try{
    //Some code here
}
catch(DivideByZeroException dz){
    AlerUserDivideByZerohappened();
}
catch(Exception e){
    treatGeneralException(e);
}
finally{
    //if a IO operation here i close the hanging handlers for example
}
1
Sorcerer86pt

2番目の方法は良い方法です。

エラーを表示したくない場合は、関連しないランタイム例外(エラー)を表示してアプリケーションのユーザーを混乱させたくない場合は、単にエラーをログに記録するだけで、技術チームは問題を探して解決できます。

try
{
  //do some work
}
catch(Exception exception)
{
   WriteException2LogFile(exception);//it will write the or log the error in a text file
}

私はあなたがあなたの全体のアプリケーションのための第二のアプローチに行くことを勧めます。

1
Pranay Rana

ベストプラクティスは、エラーが発生したときに例外をスローすることです。エラーが発生したため、隠してはいけません。

しかし現実には、これを隠したいときにはいくつかの状況が考えられます。

  1. あなたはサードパーティのコンポーネントに頼っていて、エラーが発生した場合はプログラムを続行したいです。
  2. エラーが発生した場合に続行する必要があるというビジネスケースがあります
0
Gregory Nozik

空白のcatchブロックを残すことは最悪です。エラーが発生した場合の対処方法は、次のとおりです。

  1. ファイル\ databaseなどにログインしてください。
  2. その場でそれを修正しようとする(おそらくその操作を行う別の方法を試して)
  3. 修正できない場合は、エラーがあることをユーザーに通知し、もちろん操作を中止します。
0
Stasel

私はあなたに何かを言うことができます:

スニペット#1は例外を無視しているので受け入れられません。 (何も起こらなかったようにそれを飲み込んでいる)。

そのため、何もしない、または単に再スローするようなcatchブロックを追加しないでください。

キャッチブロックはいくつかの値を追加する必要があります。たとえば、エンドユーザーへのメッセージの出力、エラーのログなどです。

通常のフロープログラムロジックには例外を使用しないでください。例えば:

入力検証など< - これは有効な例外的状況ではなく、入力項目が有効かどうかを確認するためにメソッドIsValid(myInput);を書く必要があります。

例外を回避するようにコードを設計してください。例えば:

int Parse(string input);

解析できない値をintに渡すと、このメソッドはスローして例外になります。代わりに、次のように記述します。

bool TryParse(string input,out int result); < - このメソッドは解析が成功したかどうかを示すブール値を返します。

多分これはこの質問の範囲外ですが、これがtry {} catch(){}と例外について正しい決定を下すのに役立つことを願っています。

0
Roxy'Pro

ユーザに何も言わない例外を扱う必要がある場合もあります。

私のやり方は:

  • 重大な例外(アプリケーションは役に立ちません)について、アプリケーションレベル(つまりglobal.asax)でキャッチされていない例外をキャッチすること。私がその場で捉えていないのです。アプリレベルでそれらをログに記録し、システムにその仕事をさせてください。
  • 「その場で」キャッチしていくつかの有用な情報をユーザーに表示します(間違った番号を入力したため、解析できません)。
  • 「私はバックグラウンドで更新情報をチェックしますが、サービスは実行されていません」などの限界的な問題には何もしません。

それは間違いなくベストプラクティスである必要はありません。 ;-)

0
Fanda

私にとっては、例外処理はビジネスルールと見なすことができます。明らかに、最初のアプローチは受け入れられません。 2番目のものはより良いものです、そして、文脈がそう言うならば、それは100%正しい方法であるかもしれません。今、たとえば、あなたはOutlookアドインを開発しています。アドインが未処理の例外をスローした場合、1つのプラグインが失敗してもOutlookが自分自身を破壊することはないため、Outlookユーザーはそれを認識している可能性があります。そして、あなたは何が悪かったのかを理解するのに苦労しています。したがって、この場合の2番目の方法は、私にとっては正しい方法です。例外を記録するだけでなく、エラーメッセージをユーザーに表示することもできます。これをビジネスルールと見なします。

0
Thai Anh Duc

あなたはこれらの例外の設計ガイドラインを考慮する必要があります

  • 例外投げ
  • 標準例外タイプの使用
  • 例外とパフォーマンス

https://docs.Microsoft.com/ja-jp/dotnet/standard/design-guidelines/exceptions

0
Jaider

引数なしのcatchは単にeating例外であり、役に立ちません。致命的なエラーが発生した場合はどうなりますか?引数なしでcatchを使用した場合に何が起こったのかを知る方法はありません。

CatchステートメントはFileNotFoundExceptionのようなより多くの特定の例外を捉えるべきであり、それからまさしくend _あなたはExceptionを捉えるべきです。

0
Anirudha