web-dev-qa-db-ja.com

キャッチブロックのないfinallyブロックはJavaアンチパターンですか?

次のようなコードのトラブルシューティングで、かなり苦痛なトラブルシューティングを経験しました。

try {
   doSomeStuff()
   doMore()
} finally {
   doSomeOtherStuff()
}

DoSomeStuff()が例外をスローし、その結果doSomeOtherStuff()も例外をスローしたため、問題のトラブルシューティングが困難でした。 2番目の例外(finallyブロックによってスローされた)が私のコードにスローされましたが、問題の本当の根本原因である最初の例外(doSomeStuff()からスローされた)に対するハンドルがありませんでした。

コードが代わりにこれを言っていた場合、問題はすぐに明らかになります。

try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

だから、私の質問はこれです:

キャッチブロックなしでfinallyブロックが使用されていることは、よく知られているJavaアンチパターンですか?(明らかによく知られているアンチパターンのサブクラスではないようです。 "例外を食い物にしないでください!」)

44
Jared

一般的に、いいえ、これはアンチパターンではありません。最終的にブロックするポイントは、例外がスローされるかどうかに関係なく、ものがクリーンアップされることを確認することです。例外処理の要点は、処理できない場合は、比較的クリーンな帯域外シグナリングの例外処理によって、処理できる人にバブルを発生させることです。例外がスローされた場合に確実にクリーンアップする必要があるが、現在のスコープで例外を適切に処理できない場合、これはまさに正しいことです。最終的なブロックがスローされないように、もう少し注意する必要があるかもしれません。

62
dsimcha

ここでの本当の「アンチパターン」とは、finallyブロックで、キャッチがなくてもスローできる何かを実行していることだと思います。

26
Logan Capaldo

どういたしまして。

何が問題なのかは、finally内のコードです。

最終的には常に実行され、例外をスローする可能性のあるものをそこに置くのは危険であることに注意してください(あなたが目撃したばかりです)。

16
OscarRyz

最終的にキャッチなしで試してみるのは絶対に悪いことではありません。次のことを考慮してください。

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ブロックを使用するのは完全に理にかなっていると思います。

10
Bryan Kyle

これはアンチパターンとはほど遠いものであり、メソッドの実行中に取得したリソースの割り当てを解除することが重要な場合に頻繁に行うことだと思います。

(書き込み用の)ファイルハンドルを処理するときに私が行うことの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);
}

私は次の理由でそれをそのようにするのが好きです:

  • ファイルを閉じるときに例外を無視することは完全に安全ではありません。ファイルにまだ書き込まれていないバイトがある場合、ファイルは呼び出し元が期待する状態にない可能性があります。
  • したがって、flush()メソッド中に例外が発生した場合、それは呼び出し元に伝播されますが、それでもすべてのファイルが閉じられていることを確認します。メソッドIOUtils.closeQuietly(...)は、対応するtry ... catch ... ignoremeブロックよりも冗長ではありません。
  • 複数の出力ストリームを使用する場合は、flush()メソッドの順序が重要です。他のストリームをコンストラクターとして渡すことによって作成されたストリームは、最初にフラッシュする必要があります。同じことがclose()メソッドにも当てはまりますが、私の意見では、flush()の方が明確です。
5
Ravi Wallau

キャッチブロックのないトライブロックはアンチパターンだと思います。 「キャッチなしで最後に持ってはいけない」と言うことは、「キャッチなしで試してはいけない」のサブセットです。

1
billjamesdev

私の意見では、finallycatchが何らかの問題を示している場合が多いです。リソースのイディオムは非常に単純です。

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)を複製しません。

1
Julien Grenier
try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

それもしないでください...バグを隠しただけです(正確には隠していません...しかし、それらを処理するのが難しくなりました。Exceptionをキャッチすると、あらゆる種類のRuntimeException(NullPointerやArrayIndexOutOfBoundsなど)もキャッチします。 。

一般に、キャッチする必要のある例外(チェックされた例外)をキャッチし、テスト時に他の例外を処理します。 RuntimeExceptionsは、プログラマーエラーに使用するように設計されています。プログラマーエラーは、適切にデバッグされたプログラムでは発生しないはずです。

1
TofuBeer

_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句の後でです。

0
Juuso Ohtonen

Try/Finalは、リソースを適切に解放する方法です。 finalブロックのコードは、tryブロックに入る前に取得されたリソースまたは状態にのみ作用するため、決してスローしないでください。

余談ですが、log4Jはほぼアンチパターンだと思います。

実行中のプログラムを検査したい場合は、適切な検査ツールを使用してください(つまり、デバッガー、IDE、または極端な意味でバイトコードウィーバーを使用しますが、ログステートメントを数行ごとに配置しないでください!)。

2つの例では、最初の例が正しいように見えます。 2つ目は、ロガーコードを含み、バグを導入します。 2番目の例では、最初の2つのステートメントによって例外がスローされた場合に例外を抑制します(つまり、例外をキャッチしてログに記録しますが、再スローはしません。これはlog4jの使用法で非常に一般的であり、アプリケーション設計の実際の問題です。基本的に変更を加えると、基本的に例外がないかのように先に進むため、システムが処理するのが非常に難しい方法でプログラムが失敗します(VB基本的なエラー履歴書のようなもの)次の構成)。

0
user188208