ぶつかったテクニックについてアドバイスをお願いします。コード スニペットを見れば簡単に理解できますが、次の段落でもう少し詳しく説明します。
「コード サンドイッチ」イディオムの使用は、リソース管理を扱うのに一般的です。C++ の RAII イディオムに慣れていた私は、Java に切り替えたところ、例外セーフなリソース管理の結果、コードが深くネストされていることがわかりました。このコードでは、通常の制御フローを把握するのに非常に苦労しました。
どうやら ( Java データ アクセス: この Java データ アクセス コードのスタイルは良いのか、それとも最後に試行しすぎているのか?、Java io の醜い try-finally ブロックなど) 私は一人ではありません。
これに対処するために、さまざまな解決策を試しました。
プログラムの状態を明示的に維持します:
resource1aquired
,fileopened
..., そして条件付きでクリーンアップします:if (resource1acquired) resource1.cleanup()
... しかし、明示的な変数でプログラムの状態を複製することは避けます。ランタイムは状態を認識しており、私はそれを気にしたくありません。ネストされたすべてのブロックを関数でラップ - 制御フローをたどるのがさらに難しくなり、非常に厄介な関数名になります:
runResource1Acquired( r1 )
、runFileOpened( r1, file )
、 ...
そして最後に、コード サンドイッチに関するいくつかの研究論文に(概念的に) 裏付けられたイディオムにたどり着きました。
これの代わりに:
// (pseudocode)
try {
connection = DBusConnection.SessionBus(); // may throw, needs cleanup
try {
exported = false;
connection.export("/MyObject", myObject ); // may throw, needs cleanup
exported = true;
//... more try{}finally{} nested blocks
} finally {
if( exported ) connection.unExport( "/MyObject" );
}
} finally {
if (connection != null ) connection.disconnect();
}
ヘルパー構成を使用すると、補正コードがオリジネーターのすぐ隣にある、より直線的な構成に到達する可能性があります。
class Compensation {
public void compensate(){};
}
compensations = new Stack<Compensation>();
ネストされたコードは線形になります。
try {
connection = DBusConnection.SessionBus(); // may throw, needs cleanup
compensations.push( new Compensation(){ public void compensate() {
connection.disconnect();
});
connection.export("/MyObject", myObject ); // may throw, needs cleanup
compensations.push( new Compensation(){ public void compensate() {
connection.unExport( "/MyObject" );
});
// unfolded try{}finally{} code
} finally {
while( !compensations.empty() )
compensations.pop().compensate();
}
私はうれしく思いました。例外的なパスがいくつあっても、制御フローは線形のままであり、クリーンアップ コードは視覚的に元のコードの隣に表示されます。その上、人為的に制限されたメソッドを必要としないため、closeQuietly
柔軟性が向上します (つまり、Closeable
オブジェクトだけでなくDisconnectable
、Rollbackable
その他のものも)。
しかし...
この手法については、他の場所で言及されていません。だからここに質問があります:
このテクニックは有効ですか?どのようなバグが見られますか?
どうもありがとう。