Fortifyのような静的コードアナライザーは、finally
ブロック内で例外がスローされたときに「文句を言う」_Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
_と言います。通常私はこれに同意します。しかし最近、私はこのコードに遭遇しました:
_SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
_
writer
を適切に閉じることができない場合、writer.close()
メソッドは例外をスローします。 (おそらく)書き込み後にファイルが保存されなかったため、例外がスローされます。
追加の変数を宣言し、writer
を閉じるときにエラーが発生した場合に設定して、finallyブロックの後に例外をスローすることができます。しかし、このコードは問題なく機能し、変更するかどうかはわかりません。
finally
ブロック内で例外をスローすることの欠点は何ですか?
基本的に、finally
句はリソースの適切な解放を保証するためにあります。ただし、finallyブロック内で例外がスローされた場合、その保証はなくなります。さらに悪いことに、コードのメインブロックが例外をスローする場合、finally
ブロックで発生した例外はそれを非表示にします。エラーはclose
の呼び出しが原因であるように見えますが、実際の理由ではありません。
一部の人々は、ネストされた例外ハンドラの厄介なパターンに従って、finally
ブロックでスローされた例外を飲み込みます。
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} finally {
if (writer!= null) {
try {
writer.close();
} catch (...) {
}
}
}
古いバージョンのJavaでは、この「安全な」クリーンアップを行うクラスでリソースをラップすることにより、このコードを「単純化」できます。私の親友は、それぞれがリソースをクリーンアップするためのロジックを提供する匿名タイプのリストを作成します。次に、彼のコードは単にリストをループし、finally
ブロック内のdisposeメソッドを呼び出します。
Travis Parksが言ったことは、finally
ブロックの例外がtry...catch
ブロックからの戻り値または例外を消費するということです。
Java 7を使用している場合は、問題を解決するには、 リソースを試す ブロック。ドキュメントによると、リソースがJava.lang.AutoCloseable
を実装している限り(ほとんどのライブラリライター/リーダーは現在実装しています)、try-with-resourcesブロックがリソースを閉じます。ここでの追加の利点は、閉じるときに発生する例外が抑制され、元の戻り値または例外が渡されることです。
から
FileWriter writer = null;
try {
writer = new FileWriter("myFile.txt");
writer.write("hello");
} catch(...) {
// return/throw new exception
} finally {
writer.close(); // an exception would consume the catch block's return/exception
}
に
try (FileWriter writer = new FileWriter("myFile.txt")) {
writer.write("hello");
} catch(...) {
// return/throw new exception, always gets returned even if writer fails to close
}
http://docs.Oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
これは、リソースを試してみるアプローチでもこれらの警告が存在する可能性がある理由に対する概念的な回答になるでしょう。また、残念ながら、実現したいと考えている簡単な種類のソリューションではありません。
エラー回復は失敗できません
finally
は、トランザクションが成功したか失敗したかに関係なく実行されるトランザクション後の制御フローをモデル化します。
失敗した場合、finally
は、完全に回復される前に、エラーから回復するmiddleで実行されるロジックをキャプチャします( catch
目的地に到達する前に)。
エラーからの回復のmiddleでエラーが発生するという概念的な問題を想像してみてください。
トランザクションをコミットしようとしているデータベースサーバーを想像してみてください。途中で失敗します(サーバーが途中でメモリ不足になったとします)。ここで、サーバーは、何も起こらなかったかのように、トランザクションをある時点までロールバックしたいと考えています。それでも、ロールバックのプロセスでさらに別のエラーが発生したとします。これで、データベースに対して半分コミットされたトランザクションが作成されます。トランザクションの原子性と不可分な性質が失われ、データベースの整合性が損なわれます。
この概念的な問題は、Cが手動のエラーコード伝播であるか、C++が例外とデストラクタであるか、またはJavaが例外でfinally
であるかに関係なく、エラーを処理するすべての言語に存在します。
finally
は、例外が発生する過程でデストラクタがC++で失敗できないのと同じ方法でそれを提供する言語では失敗できません。
この概念的で難しい問題を回避する唯一の方法は、トランザクションをロールバックし、途中でリソースを解放するプロセスで、再帰的な例外/エラーが発生しないようにすることです。
したがって、ここでの唯一の安全な設計は、writer.close()
が失敗する可能性がない設計です。通常、そのようなことが回復の途中で失敗し、それが不可能になるシナリオを回避する方法が設計にあります。
残念ながらそれが唯一の方法です-エラー回復は失敗できません。これを確実にする最も簡単な方法は、これらの種類の「リソース解放」および「逆副作用」機能を失敗させないようにすることです。簡単ではありません-適切なエラー回復はhardであり、残念ながらテストするのも困難です。しかし、それを達成する方法は、「破棄」、「閉じる」、「シャットダウン」、「ロールバック」などの機能がプロセスで外部エラーに遭遇しないようにすることです。既存のエラーから回復する途中で呼び出される。
例:ロギング
finally
ブロック内のログを記録するとします。 ロギングが失敗しない限り、これはしばしば大きな問題になるでしょう。ファイルにデータを追加する必要がある可能性があるため、ロギングはほぼ確実に失敗する可能性があり、失敗する多くの理由を簡単に見つけることができます。
したがって、ここでの解決策は、finally
ブロックで使用されるロギング関数が呼び出し元にスローされないようにすることです(それは失敗しますが、スローされません)。どうすればよいでしょうか?ネストされたtry/catchブロックがある場合、最終的にのコンテキスト内でスローを許可する言語の場合、例外を飲み込んで例外をエラーコードに変換することにより、呼び出し元へのスローを回避する1つの方法です。おそらく、別のプロセスまたはスレッドでログ記録を実行すると、既存のエラー回復スタックの巻き戻しの外で個別に失敗する可能性があります。エラーが発生する可能性なしにそのプロセスと通信できれば、安全性の問題もこのシナリオでのみ発生するため、例外が発生しても安全です(同じスレッド。
この場合、ログに失敗して何もしないことが世界の終わりではないためにスローされないことを条件に、ログの失敗を回避できます(リソースのリークや副作用のロールバックに失敗しないなど)。
とにかく、ソフトウェアを本当に例外安全にするのがどれほど信じがたいほど難しいかは、すでに想像し始めることができると思います。最もミッションクリティカルなソフトウェアを除いて、これを最大限に追求する必要はないかもしれません。ただし、非常に汎用的なライブラリの作成者でさえ、ここで手を出し、ライブラリを使用してアプリケーションの例外安全性全体を破壊することが多いため、例外安全性を本当に実現する方法に注意する価値があります。
SomeFileWriter
SomeFileWriter
がclose
の内部でスローできる場合、既存の例外からの回復を伴うコンテキストで閉じようとしない限り、通常は例外処理と互換性がないと思います。そのコードがあなたのコントロールの外にある場合、私たちはSOLかもしれませんが、この露骨な例外安全性の問題を著者に通知する価値があります。それがあなたのコントロールの範囲内にある場合、私の主な推奨事項は必要な手段で閉じるのに失敗しないようにしてください。
オペレーティングシステムが実際にファイルを閉じるのに失敗する場合を想像してみてください。これで、シャットダウン時にファイルを閉じようとするプログラムはシャットダウンに失敗します。私たちが今やるべきことは、アプリケーションを開いたままにして(おそらくない)、ファイルリソースをリークし、問題を無視することです(それほど重要でない場合は問題ないかもしれません)。最も安全な設計:ファイルのクローズに失敗しないようにしてください。
これは、ケースバイケースで対処する必要があることだと思います。場合によっては、アナライザーが言っていることは正しいですが、あなたが持っているコードはそれほど優れておらず、再考が必要です。しかし、投げる、あるいは投げ直すことが最善であるかもしれない他の場合があるかもしれません。それはあなたが委任できるものではありません。