返品後に作業を行うためのfinally節の使用は、スタイルが悪い/危険ですか?


30

イテレーターの作成の一環として、次のコードを作成していることに気付きました(エラー処理を削除)

public T next() {
  try {
    return next;
  } finally {
    next = fetcher.fetchNext(next);
  }
}

読むよりもやや読みやすい

public T next() {
  T tmp = next;
  next = fetcher.fetchNext(next);
  return tmp;
}

私はそれが読みやすさの違いがそれほど圧倒的ではないかもしれない単純な例であることを知っていますが、例外が含まれていないこのような場合にtry-finallyを使用するのが悪いかどうかについての一般的な意見に興味がありますコードを簡素化するときに実際に優先される場合。

悪い場合:なぜ?スタイル、パフォーマンス、落とし穴、...?

結論 あなたのすべての答えに感謝します!(少なくとも私にとって)結論は、最初の例は一般的なパターンであれば読みやすかったかもしれないが、そうではないということだと思います。そのため、目的外の構造を使用することで生じる混乱と、場合によっては難読化された例外フローが、単純化よりも重要になります。


10
私は個人的に最初のものよりも2番目のものを理解することができるでしょうが、私はめったにfinallyブロックを使用しません。
クリスチャンマン

2
return current = fetcher.fetchNext(current); //これについてはどうですか、別名プリフェッチが本当に必要ですか?
スカーフリッジ

1
@scarfridgeこの例はの実装に関するものIteratorであり、実際hasNext()に動作させるためにはプリフェッチのようなものが必要です。自分で試してみてください。
-maaartinus

1
@maaartinus私はそれを知っています。hasNext()は単純にtrueを返す場合があります。たとえば、あられのシーケンスや、次の要素の取得に非常に時間がかかる場合があります。後者の場合、遅延初期化と同様に、必要に応じて要素を取得することに頼るべきです。
スカーフリッジ

1
@scarfridgeの一般的な使用の問題Iteratorは、値を取得する必要があることですhasNext()(取得することが、存在するかどうかを確認する唯一の方法であることが非常に多いためです)next()
-maaartinus

回答:


18

個人的には、コマンドクエリ分離のアイデアを考えます。すぐにわかると、next()には2つの目的があります。広告されたnext要素を取得する目的と、nextの内部状態を変更する隠れた副作用です。だから、あなたがやっているのは、メソッドの本体で公示された目的を実行し、finally節の隠された副作用に取り組むことです...

要するに、コードがどれほど理解しやすいかということです。この場合、答えは「meh」です。単純なreturnステートメントを「試行」してから、フェイルセーフエラーリカバリ用のコードブロックで隠れた副作用を実行しています。あなたがしているのは「賢い」ことであり、「賢い」コードはしばしばメンテナーに「なにが…なんてことだと思う」とつぶやくように誘います。あなたのコードを読んでいる人々が「うん、うん、理にかなっている、はい...」とつぶやくのに良い

それでは、アクセサコールから状態突然変異を分離したらどうなるでしょうか?あなたが懸念している読みやすさの問題は議論の余地があると思いますが、それがあなたのより広い抽象化にどのように影響するかわかりません。とにかく考慮すべきこと。


1
「賢い」コメントの場合は+1。それはこの例を非常に正確に捉えています。

2
next()の 'return and advance'アクションは、扱いにくいJavaイテレーターインターフェースによってOPに強制されます。
ケビンクライン

37

純粋にスタイルの観点から、私はこれらの3行を考える:

T current = next;
next = fetcher.getNext();
return current;

... try / finallyブロックよりも明確で短い。例外がスローされることを期待していないので、tryブロックを使用すると人々を混乱させるだけです。


2
try / catch / finallyブロックも余分なオーバーヘッドを追加する可能性があり、例外をスローしていない場合でもjavacまたはJITコンパイラーがそれらを除去するかどうかはわかりません。
マルティンヴェルブルク

2
@MartijnVerburgええ、javacは例外ブロックにエントリを追加し、tryブロックが空の場合でもfinallyコードを複製します。
ダニエルルバロフ

@ダニエル-おかげで私はjavapを実行するのが面倒でした:-)
Martijn Verburg

@Martijn Verburg:AFAIK、try-catch-finalltブロックは、実際に例外がスローされない限り本質的に無料です。ここで、javacはJITが処理するため、何も試行せず、最適化する必要もありません。
-maaartinus

@maaartinus-理にかなっているおかげで-OpenJDKソースコードを再度読む必要があります:
Martijn Verburg

6

それはfinallyブロック内のコードの目的に依存します。標準的な例は、読み取り/書き込み後にストリームを閉じることです。これは、常に行わなければならない「クリーンアップ」のようなものです。オブジェクト(この場合はイテレータ)を有効な状態に復元すると、IMHOもクリーンアップとしてカウントされるため、ここで問題は発生しません。return戻り値が見つかったらすぐにOTOHを使用し、finallyブロックに多くの無関係なコードを追加すると、目的がわかりにくくなり、理解しにくくなります。

「例外が含まれていない」場合は、問題なく使用できます。コードがスローできるだけで、それらを処理する予定がない場合は、try...finallyaなしで使用するのが非常に一般的です。場合によっては、finallyは単なる保護手段であり、プログラムのロジックについては、例外がスローされないことがわかっています(従来の「これは絶対に起こらない」条件)。catchRuntimeException

落とし穴:tryブロック内で発生した例外により、ブロックがfinally実行されます。これにより、一貫性のない状態になる可能性があります。したがって、returnステートメントが次のようなものである場合:

return preProcess(next);

このコードは例外を発生させ、fetchNextそれでも実行されます。OTOHのようにコーディングした場合:

T ret = preProcess(next);
next = fetcher.fetchNext(next);
return ret;

その後、実行されません。tryコードは例外を決して発生させないと仮定していることを知っていますが、これよりも複雑な場合はどうすれば確認できますか?イテレータが長命のオブジェクトである場合、現在のスレッドで回復不可能なエラーが発生しても、イテレータは常に有効な状態を維持することが重要であるため、既存のオブジェクトは存在し続けます。そうでなければ、それは本当に重要ではありません...

パフォーマンス:そのようなコードを逆コンパイルして内部でどのように機能するかを見るのは面白いでしょうが、JVMについて十分な知識がなく、十分に推測することもできません...例外は通常「例外的」なので、処理するコードは速度を最適化する必要はありません(したがって、プログラムの通常の制御フローで例外を使用しないようにアドバイスする必要があります)が、私は知らないfinally


それでは、実際にこの方法での使用を避けるためのかなりの理由を提供していませんfinallyか?あなたが言うように、コードは望ましくない結果をもたらすことになります。さらに悪いことに、あなたはおそらく例外についても考えていないでしょう。
アンドレスF.

2
通常の制御フロー速度は、例外処理構造の影響をあまり受けません。パフォーマンスのペナルティは、実際に例外をスローおよびキャッチした場合にのみ支払われ、通常の操作でtryブロックに入るときやfinallyブロックに入るときではありません。
イフェルドブルム

1
@AndresF。本当ですが、それはどのクリーンアップコードでも有効です。たとえば、開いているストリームへの参照がnullバグのあるコードによって設定されているとします。それから読み込もうとすると例外が発生し、finallyブロック内でそれを閉じようとすると別の例外が発生します。また、これは、計算の状態が重要ではない状況であり、例外が発生したかどうかに関係なくストリームを閉じたい場合です。そのような他の状況もあるかもしれません。このため、私はそれを決して使用しないことを推奨するのではなく、有効な使用の落とし穴として扱っています。
mgibsonbr

また、tryブロックとfinallyブロックの両方が例外をスローする場合、tryの例外が誰にも見られないという問題もありますが、おそらくそれが問題の原因です。
ハンスピーターStörr12年

3

タイトル文:「...帰国後に作業を行うための最終節...」は偽です。finallyブロックは、関数が戻る前に発生します。それが実際に最終的に重要なポイントです。

ここで悪用しているのは評価の順序です。ここで、nextの値は、変更する前に戻るために保存されます。これは一般的な慣行ではなく、私の意見では間違っているため、コードが非シーケンシャルになり、従うのがはるかに難しくなります。

finallyブロックの用途は、スローされる例外に関係なく何らかの結果が発生する例外処理です。たとえば、リソースのクリーンアップ、DB接続のクローズ、ファイル/ソケットのクローズなどです。


+1、言語仕様が値が返される前に保存されることを保証していても、読者の期待に反する可能性があるため、合理的に可能な場合は避けるべきであると付け加えます。コーディング...とハードの倍れるデバッグ
jmoreno

0

(一般的に、あなたの例ではなく)tryの一部が例外をスローする可能性があるため、私は本当に慎重です。望まないときに実行します。

そして、他のワームの缶は、一般に、最終的にいくつかの有用なアクションが例外を投げることができるので、あなたは本当に厄介な方法で終わることができます。

これらのため、それを読んでいる人はこれらの問題について考えなければならないので、理解するのはより困難です。

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