次のようなコードのトラブルシューティングで、かなり苦痛なトラブルシューティングを経験しました。
try {
doSomeStuff()
doMore()
} finally {
doSomeOtherStuff()
}
DoSomeStuff()が例外をスローし、その結果doSomeOtherStuff()も例外をスローしたため、問題のトラブルシューティングが困難でした。 2番目の例外(finallyブロックによってスローされた)が私のコードにスローされましたが、問題の本当の根本原因である最初の例外(doSomeStuff()からスローされた)に対するハンドルがありませんでした。
コードが代わりにこれを言っていた場合、問題はすぐに明らかになります。
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
だから、私の質問はこれです:
キャッチブロックなしでfinallyブロックが使用されていることは、よく知られているJavaアンチパターンですか?(明らかによく知られているアンチパターンのサブクラスではないようです。 "例外を食い物にしないでください!」)
一般的に、いいえ、これはアンチパターンではありません。最終的にブロックするポイントは、例外がスローされるかどうかに関係なく、ものがクリーンアップされることを確認することです。例外処理の要点は、処理できない場合は、比較的クリーンな帯域外シグナリングの例外処理によって、処理できる人にバブルを発生させることです。例外がスローされた場合に確実にクリーンアップする必要があるが、現在のスコープで例外を適切に処理できない場合、これはまさに正しいことです。最終的なブロックがスローされないように、もう少し注意する必要があるかもしれません。
ここでの本当の「アンチパターン」とは、finally
ブロックで、キャッチがなくてもスローできる何かを実行していることだと思います。
どういたしまして。
何が問題なのかは、finally内のコードです。
最終的には常に実行され、例外をスローする可能性のあるものをそこに置くのは危険であることに注意してください(あなたが目撃したばかりです)。
最終的にキャッチなしで試してみるのは絶対に悪いことではありません。次のことを考慮してください。
InputStream in = null;
try {
in = new FileInputStream("file.txt");
// Do something that causes an IOException to be thrown
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Nothing we can do.
}
}
}
例外がスローされ、このコードがその処理方法を知らない場合、例外は呼び出しスタックを呼び出し元にバブルアップする必要があります。この場合でもストリームをクリーンアップしたいので、キャッチなしでtryブロックを使用するのは完全に理にかなっていると思います。
これはアンチパターンとはほど遠いものであり、メソッドの実行中に取得したリソースの割り当てを解除することが重要な場合に頻繁に行うことだと思います。
(書き込み用の)ファイルハンドルを処理するときに私が行うことの1つは、IOUtils.closeQuietlyメソッドを使用してストリームを閉じる前にストリームをフラッシュすることです。これは例外をスローしません。
OutputStream os = null;
OutputStreamWriter wos = null;
try {
os = new FileOutputStream(...);
wos = new OutputStreamWriter(os);
// Lots of code
wos.flush();
os.flush();
finally {
IOUtils.closeQuietly(wos);
IOUtils.closeQuietly(os);
}
私は次の理由でそれをそのようにするのが好きです:
キャッチブロックのないトライブロックはアンチパターンだと思います。 「キャッチなしで最後に持ってはいけない」と言うことは、「キャッチなしで試してはいけない」のサブセットです。
私の意見では、finally
とcatch
が何らかの問題を示している場合が多いです。リソースのイディオムは非常に単純です。
acquire
try {
use
} finally {
release
}
Javaでは、ほとんどどこからでも例外が発生する可能性があります。多くの場合、acquireはチェック済みの例外をスローします。これを処理する賢明な方法は、いくらをキャッチすることです。いくつか試さないでください。恐ろしいヌルチェック。
あなたが本当に肛門になるつもりなら、例外の中に暗黙の優先順位があることに注意する必要があります。たとえば、ThreadDeathは、取得/使用/解放に由来するかどうかに関係なく、すべてを破壊する必要があります。これらの優先順位を正しく処理することは見苦しいです。
したがって、Execute Aroundイディオムを使用して、リソース処理を抽象化します。
私はtry/finallyを次の形式で使用します:
try{
Connection connection = ConnectionManager.openConnection();
try{
//work with the connection;
}finally{
if(connection != null){
connection.close();
}
}
}catch(ConnectionException connectionException){
//handle connection exception;
}
私はこれをtry/catch/finally(+最後にネストされたtry/catch)よりも好みます。もっと簡潔だと思いますし、catch(Exception)を複製しません。
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
それもしないでください...バグを隠しただけです(正確には隠していません...しかし、それらを処理するのが難しくなりました。Exceptionをキャッチすると、あらゆる種類のRuntimeException(NullPointerやArrayIndexOutOfBoundsなど)もキャッチします。 。
一般に、キャッチする必要のある例外(チェックされた例外)をキャッチし、テスト時に他の例外を処理します。 RuntimeExceptionsは、プログラマーエラーに使用するように設計されています。プログラマーエラーは、適切にデバッグされたプログラムでは発生しないはずです。
_try-finally
_は、メソッドに複数のreturn
ステートメントがある場合に、コピーアンドペーストコードを減らすのに役立つ場合があります。次の例(Android Java)について考えてみます。
_boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) {
Cursor cursor = db.rawQuery("SELECT * FROM table", null);
if (cursor != null) {
try {
if (cursor.getCount() == 0) {
return false;
}
} finally {
// this will get executed even if return was executed above
cursor.close();
}
}
// database had rows, so do something...
return true;
}
_
finally
句がない場合は、cursor.close()
を2回書き込む必要があります。_return false
_の直前と周囲のif
句の後でです。
Try/Finalは、リソースを適切に解放する方法です。 finalブロックのコードは、tryブロックに入る前に取得されたリソースまたは状態にのみ作用するため、決してスローしないでください。
余談ですが、log4Jはほぼアンチパターンだと思います。
実行中のプログラムを検査したい場合は、適切な検査ツールを使用してください(つまり、デバッガー、IDE、または極端な意味でバイトコードウィーバーを使用しますが、ログステートメントを数行ごとに配置しないでください!)。
2つの例では、最初の例が正しいように見えます。 2つ目は、ロガーコードを含み、バグを導入します。 2番目の例では、最初の2つのステートメントによって例外がスローされた場合に例外を抑制します(つまり、例外をキャッチしてログに記録しますが、再スローはしません。これはlog4jの使用法で非常に一般的であり、アプリケーション設計の実際の問題です。基本的に変更を加えると、基本的に例外がないかのように先に進むため、システムが処理するのが非常に難しい方法でプログラムが失敗します(VB基本的なエラー履歴書のようなもの)次の構成)。