階層型アプリケーションで例外を処理するための推奨されるアプローチまたはベストプラクティスは何ですか?
try/catch
ブロック?簡単な例を考えてみましょう。ビジネスレイヤーを呼び出し、データレイヤーを呼び出すUIがあるとします。
//UI
protected void ButtonClick_GetObject(object sender, EventArgs e)
{
try {
MyObj obj = Business.GetObj();
}
catch (Exception ex) {
Logger.Log(ex); //should the logging happen here, or at source?
MessageBox.Show("An error occurred");
}
}
//Business
public MyObj GetObj()
{
//is this try/catch block redundant?
try {
MyObj obj = DAL.GetObj();
}
catch (Exception ex) {
throw new Exception("A DAL Exception occurred", ex);
}
}
//DAL
public MyObj GetObj()
{
//Or is this try/catch block redundant?
try {
//connect to database, get object
}
catch (SqlException ex) {
throw new Exception("A SQLException occurred", ex);
}
}
上記の例外処理についてどのような批判をしますか?
ありがとう
私の経験則では、通常、トップレベルで例外をキャッチし、そこでログに記録(またはその他の方法で報告)します。これは、エラーに関する最も多くの情報、最も重要なのは完全なスタックトレースがあるためです。
ただし、他の層で例外をキャッチする理由はいくつかあります。
SqlException
は通知しません。DatabaseUnavailableException
をスローする場合があります。 BLは、重要ではない操作についてはこれを無視するか、重要な操作に対してはそれを伝播させる可能性があります。 BLが代わりにSqlException
をキャッチした場合、DALの実装の詳細に公開されます。代わりに、DatabaseUnavailableException
をスローする可能性はDALのインターフェースの一部です。複数の層で同じエラーをログに記録することは一般的には役に立ちませんが、1つの例外を考えることができます。下位層が問題が重大であるかどうかわからない場合、警告としてログに記録できます。上位層がisクリティカルであると判断した場合、同じ問題をエラーとしてログに記録できます。
例外処理に関連して私が従ういくつかのルールは次のとおりです。
Application.ThreadException
イベントを実装する必要があります。 ASP .Netアプリケーションでは、global.asaxのApplication_Error
イベントハンドラーを実装する必要があります。これらの場所は、コードで例外をキャッチできる最上位の場所です。多くのアプリケーションでは、は、ほとんどの例外をキャッチする場所であり、ロギングに加えて、ここでは、ユーザーに表示される一般的でわかりやすいエラーメッセージウィンドウも実装する可能性があります。SqlConnection conn = null;
try
{
conn = new SqlConnection(connString);
...
}
// do not implement any catch in here. db exceptions logic should be implemented at upper levels
finally
{
// finalization code that should always be executed.
if(conn != null) conn.Dispose();
}
throw;
を使用するだけです。これにより、スタックトレースが確実に保持されます。 throw ex;
を使用すると、スタックトレースがリセットされます。最初に修正することは、一般的なException
を決してスローしないことです。
2つ目は、例外をラップする正当な理由がない限り、throw;
の代わりに throw new...
catch句で。
3番目(これは厳格なルールではありません)は、UIレイヤーの下のどのポイントでも一般的な例外をキャッチしません。 UIレイヤーは、一般的な例外をキャッチして、爆発したものの技術的な詳細ではなく、ユーザーフレンドリーなメッセージをエンドユーザーに表示できるようにする必要があります。レイヤーのより深いところで一般的な例外をキャッチすると、意図せずに飲み込まれてしまう可能性があり、追跡が非常に難しいバグが発生します。
例外処理はどのアプリケーションでも困難です。あなたはすべての例外について考え、すぐにあなたのために働くパターンに入る必要があります。例外を次のカテゴリのいずれかにグループ化しようとしています...
例:永続性フレームワークでは、不正な形式のSQLから発生する可能性のあるSQL例外をキャッチする必要があるかもしれませんが、ハードコードされたクエリを実行しています。
処理:私の経験では、ほとんどの例外はこのカテゴリに分類されます。少なくとも、それらをログに記録します。さらに良いことに、それらをログに記録する例外処理サービスに送信します。その後、それらを別の方法でログに記録したり、別の方法で何かを実行したりする場合は、1か所で変更できます。また、UIレイヤーに何らかのエラーが発生したことを示すフラグを送信して、操作を再試行する必要がある場合もあります。たぶんあなたは管理者にメールを送ります。
また、サーバーでの寿命が続くように、何かを上位層に戻す必要があります。これがデフォルト値であるか、nullである可能性があります。たぶん、操作全体をキャンセルする方法があります。
もう1つの選択肢は、例外処理サービスに2つの処理方法を与えることです。 handleUnexpectedException()
メソッドはユーザーに通知しますが、例外を再スローしません。スタックを自分で巻き戻すか、何らかの方法で続行できる場合は、デフォルト値を返すことができます。 handleFatalException()
メソッドは、ユーザーに通知し、ある種の例外を再スローします。これにより、例外のスローによってスタックが巻き戻されるようになります。
例:ユーザーがfoobarウィジェットを更新して新しい名前を付けようとしていますが、必要な名前のfoobarウィジェットが既に存在します。
Handling:この場合、例外をユーザーに戻す必要があります。この場合、UIレイヤーに到達するまで、例外をスローし続けることができます(または、キャッチすらしないでください)。その後、UIレイヤーは例外の処理方法を認識している必要があります。これらの例外を文書化して、あなた(またはUIを作成している人)がそれらが存在することを認識し、それらを予期することを知っていることを確認してください。
例:リモートサービス呼び出しを行い、リモートサービスがタイムアウトしましたが、リモートサービスに呼び出しの履歴があることがわかっているので、呼び出しをやり直す必要があります。
処理:これらの例外は最初のカテゴリから始まる場合があります。アプリケーションがしばらくの間使用された後、実際にそれを処理するための良い方法があることに気づきます。楽観的ロックや中断された例外からの例外のように、例外をキャッチしてそれを使って何かをすることは、ビジネスの一部にすぎない場合があります。このような場合は、例外を処理してください。言語(Javaだと思います)がチェックされた例外とチェックされていない例外を区別する場合は、これらを常にチェックされた例外にすることをお勧めします。
上記の質問に対処するために、ユーザーに通知するサービスに最初の例外を委任します。MyObjのオブジェクトの種類(設定など)によっては、これを致命的ではない例外にしてデフォルト値を返す場合があります。それができない場合(ユーザーアカウントなど)、これを致命的な例外にする可能性があるため、心配する必要はありません。
ユーザーには明確で理解しやすいエラーメッセージのみが表示されるため、レイヤー境界(これは管理者用)で例外をログに記録する(ファイル内など)ために、レイヤーごとに個別の例外クラス(DALException、BLException、...)を使用しています。これらの例外は、すべてのデータアクセス層によって継承されたDAlBaseで処理する必要があります。これにより、いくつかのクラスで例外処理を一元化でき、開発者はlayerexception(DALExceptionなど)のみをスローします。詳細を参照してください 多層例外処理 。