I would like some advice on a technique I bumped onto. It can be easily understood by looking at the code snippets, but I document it somewhat more in the following paragraphs.
Using the "Code Sandwich" idiom is commonplace to deal with resource management. Used to C++'s RAII idiom, I switched to Java and found my exception-safe resource management resulting in deeply nested code, in which I have a really hard time getting grip on the regular control flow.
Apparently (java data access: is this good style of java data access code, or is it too much try finally?, Java io ugly try-finally block and many more) I'm not alone.
I tried different solutions to cope with this:
maintain the program state explicitly:
resource1aquired
,fileopened
..., and cleanup conditionally:if (resource1acquired) resource1.cleanup()
... But I shun duplicating the program state in explicit variables - the runtime knows the state, and I don't want to care for it.wrap every nested block in functions - results in even harder to follow control flow, and makes for really awkward function names:
runResource1Acquired( r1 )
,runFileOpened( r1, file )
, ...
And finally I arrived at an idiom also (conceptually) backed by some research paper on code sandwiches:
Instead of this:
// (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();
}
Using a helper construction, you may arrive at a more linear construct, in which compensation code is right next to the originator.
class Compensation {
public void compensate(){};
}
compensations = new Stack<Compensation>();
And the nested code becomes linear:
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();
}
I was delighted: no matter how many exceptional paths, the control flow stays linear, and the cleanup code is visually next to the originating code. On top of that, it doesn't need an artificially restricted closeQuietly
method, which makes it more flexible (i.e. not only Closeable
objects, but also Disconnectable
, Rollbackable
and whatever others).
But...
I found no mention of this technique elsewhere. So here's the question:
Is this technique valid? What bugs do you see in it?
Thanks a lot.
compensate
implementations to handle exceptions. – Trickstertry-finally
at all - you could just adopt that idiom with the code itself, and not bother with afinally
block. Besides, manyCompensators
would naturally throw exceptions (e.g.SQLException
for closing a DB resource); every implementation have to catch and swallow all exceptions individually, rather than yourfinally
block handling it consistently in one place. It's not optional for them to do this (a runtime exception would breach their specification, and that could happen any time) so you'd just be encouraging copy-paste. – Muskellungescope(exit)
statement – Attenuant