最終的に内部で例外をスローする


27

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ブロック内で例外をスローすることの欠点は何ですか?


4
これがJavaであり、Java 7を使用できる場合は、ARMブロックで問題を解決できるかどうかを確認してください。
ランデイ

@Landei、これで解決しますが、残念ながらJava 7は使用していません。
superM13年

あなたが示したコードは、「finallyブロック内でthrowステートメントを使用する」ものではなく、そのため、論理的な進行は問題ありません。
マイク

@ Mike、Fortifyが示す標準の概要を使用しましたが、最終的には直接または間接的に例外がスローされます。
superM

残念ながら、try-with-resourcesブロックはFortifyによって最終的に内部でスローされた例外としても検出されます。最終的資源、そして今、そのような各ステートメントは、セキュリティ上の脅威としてはFortifyが報告します。..
mmona

回答:


18

基本的に、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メソッドを呼び出します。


1
+1私はその厄介なコードを使用する人たちの中にいますが。しかし、正当な理由から、例外が重大でない場合にのみ、これを常に行うとは限りません。
superM

2
私も自分でそれをやっています。リソースマネージャー全体(匿名または非匿名)を作成すると、コードの目的が損なわれる場合があります。
トラビスパーク

私からも+1。あなたは基本的に私が何をしようとしていたかを正確に言ったが、より良い。JavaのStream実装の一部は、close()で実際に例外をスローできないが、一部の例外は例外をスローするため、例外を宣言することに注意してください。そのため、状況によっては、実際に必要になることのないcatchブロックを追加する場合があります。
vaughandroid

1
finallyブロック内でtry / catchブロックを使用するのは何が厄介ですか?私はむしろ、すべての接続とストリームなどを静かに閉じることができるようにすべてのコードを肥大化させるよりもむしろそれをやりたいと思います-それ自体、接続またはストリームを閉じているかどうかわからないという新しい問題を導入します( ...あなたが実際に知っているかについて何かをするようなことがあり、何かである-または何でも)例外が発生します
禁止-ジオエンジニアリング

10

トラビスパークスが言ったことは、finallyブロック内の例外がtry...catchブロックからの戻り値または例外を消費するということです。

ただし、Java 7を使用している場合は、try-with-resourcesブロックを使用して問題を解決できます。ドキュメントによると、リソースが実装されている限り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


1
これは時々役に立ちます。しかし、ファイルを閉じることができないときに実際に例外をスローする必要がある場合、このアプローチは問題を隠します。
superM

1
@superM実際、try-with-resourcesはからの例外を隠しませんclose()。「それを閉じるときに発生する例外は抑制され、元の戻り値または例外が渡されます」- これは単にtrueではありませんclose()メソッドからの例外は、別の例外がtry / catchブロックからスローされる場合にのみ抑制されます。そのため、tryブロックが例外をスローしない場合、close()メソッドからの例外がスローされます(戻り値は返されません)。現在のcatchブロックからもキャッチできます。
ルスランステルマチェンコ

0

これはケースバイケースで対処する必要があるものだと思います。場合によっては、アナライザーが言っていることは、あなたが持っているコードがそれほど大きくなく、再考する必要があるという点で正しいです。しかし、投げる、または再投げさえすることが最良の場合、他のケースがあるかもしれません。それはあなたが命じることができるものではありません。


0

これは、これらの警告がtry-with-resourcesアプローチでも存在する理由に対する概念的な答えになるでしょう。また、残念ながら、あなたが達成したいと思っている簡単な種類のソリューションではありません。

エラー回復は失敗できません

finally トランザクションの成功または失敗に関係なく実行されるトランザクション後の制御フローをモデル化します。

失敗した場合、エラーが完全に回復する前(宛先に到達する前)に、エラーからの回復finally途中で実行されるロジックをキャプチャしますcatch

エラーから回復する途中でエラーが発生するという概念的な問題を想像してください。

トランザクションをコミットしようとしているデータベースサーバーを想像してください。途中で失敗します(サーバーが途中でメモリを使い果たしたなど)。サーバーは、何も起こらなかったように、トランザクションをポイントにロールバックしたいと考えています。しかし、ロールバックのプロセスでさらに別のエラーが発生することを想像してください。これで、データベースに対する半分コミットされたトランザクションができあがります。トランザクションの不可分性と不可分な性質が壊れ、データベースの整合性が損なわれます。

この概念的な問題は、Cが手動のエラーコード伝播を使用している場合でも、C ++で例外とデストラクタを使用している場合でも、Javaを例外およびで使用している場合でもエラーを処理する言語に存在しますfinally

finally デストラクタが例外に遭遇する過程でC ++で失敗するのと同じ方法でそれを提供する言語で失敗することはできません。

この概念的で難しい問題を回避する唯一の方法は、トランザクションをロールバックし、途中でリソースを解放するプロセスで、再帰的な例外/エラーが発生しないようにすることです。

したがって、ここでの唯一の安全な設計は、writer.close()おそらく失敗しない設計です。通常、設計では、リカバリの途中でそのようなことが失敗して不可能になるシナリオを回避する方法があります。

残念ながら、これが唯一の方法です。エラーリカバリが失敗することはありません。これを確実にする最も簡単な方法は、これらの種類の「リソース解放」および「逆副作用」機能が失敗しないようにすることです。簡単ではありません。適切なエラー回復は難しく、残念ながらテストも困難です。しかし、それを達成する方法は、「破棄」、「閉じる」、「シャットダウン」、「ロールバック」などの関数がプロセスで外部エラーに遭遇しないようにすることです。既存のエラーから回復する途中で呼び出されます。

例:ロギング

finallyブロック内のデータをログに記録するとします。これは、ロギングが失敗しない限り、大きな問題になることがよくあります。ファイルにさらにデータを追加したい場合があるため、ロギングはほぼ確実に失敗する可能性があり、失敗する多くの理由を簡単に見つけることができます。

したがって、ここでの解決策は、finallyブロックで使用されるロギング関数が呼び出し元にスローできないようにすることです(失敗する可能性はありますが、スローされません)。どうすればいいですか?入れ子になったtry / catchブロックが存在する場合、言語で最終的にコンテキスト内でのスローが許可されている場合、例外を飲み込んでエラーコードに変えることで呼び出し元へのスローを回避する1つの方法になります。個別に失敗し、既存のエラー回復スタックの外側で失敗する可能性があるプロセスまたはスレッド。エラーが発生する可能性なしにそのプロセスと通信できる限り、同じスレッド内から再帰的にスローする場合にのみ安全性の問題がこのシナリオに存在するため、例外セーフでもあります

この場合、ログに失敗して何もしないことが世界の終わりではないため、スローされない限り、ログの失敗を回避できます(リソースのリークや副作用のロールバックに失敗しないなど)。

とにかく、ソフトウェアを例外セーフにすることは本当に信じられないほど難しいと想像し始めることができると確信しています。最もミッションクリティカルなソフトウェアを除き、すべてを最大限に追求する必要はないかもしれません。しかし、非常に汎用的なライブラリ作成者でさえも、ライブラリを使用してアプリケーションの例外安全性全体を破壊することが多いため、本当に例外安全性を達成する方法に注意する価値があります。

SomeFileWriter

SomeFileWriterinsideをスローできる場合close、既存の例外からの回復を伴うコンテキストで決して閉じようとしない限り、一般的に例外処理と互換性がないと思います。そのコードがあなたのコントロールの外にある場合、私たちはSOLかもしれませんが、この露骨な例外安全性の問題について著者に通知する価値があります。それがあなたのコントロール内にある場合、私の主な推奨事項は、それを閉じることはおそらく必要な手段によって失敗しないことを確認することです。

オペレーティングシステムが実際にファイルを閉じることができない場合を想像してください。これで、シャットダウン時にファイルを閉じようとするプログラムはシャットダウンに失敗します。今何をすべきか、アプリケーションを開いたままにし、リンボで(おそらくいいえ)ファイルリソースをリークし、問題を無視します(それほど重大でない場合は大丈夫かもしれません)。最も安全な設計:ファイルを閉じるのに失敗しないようにします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.