Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Steven Neiland
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Steven Neiland@sneiland )

Sometimes I'm Tempted To Use Try / Finally In The Worst Way

By Ben Nadel on

Yesterday, I posted a tweet about how I am sometimes tempted to use a Try / Finally block as a way to get around creating an intermediary variable. I've never actually done this, as I think it's a bad idea. But, so help me, I have been many times tempted. And, while I still don't recommend it, I thought it would be interesting to see, from a technical standpoint.


 
 
 

 
Tweet about wanting to use try/finally as a means to side-step having to create an intermediary value. 
 
 
 

Imagine a scenario in which I have a hash and I want to both delete a key from that hash and return the value stored at that key. In order to access the key, I have to perform the "get" before I perform the "delete," obviously. But, I also want to return the value; so, I have to store the value in a temporary variable so that I can return the value even after I perform the delete.

Something about this little dance just gets under my skin. I'm not sure why exactly - it's just one of those things. So, sometimes, in my dark moments, I'm tempted to side-step the intermediary variable by bastardizing the "finally" mechanism in a Try block. To demonstrate that this works, I'm going to "extract" two keys from a cache and log them to the console:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Sometimes I'm Tempted To Use Try / Finally In The Worst Way
  • </title>
  • </head>
  • <body>
  •  
  • <script type="text/javascript">
  •  
  • var cache = {
  • foo: "bar",
  • hello: "world",
  • fizz: "buzz"
  • };
  •  
  • // The extracItem() will pull the item out of the cache (ie, delete it), and
  • // then return it.
  • console.log( "Extracted:", extractItem( "foo" ) );
  • console.log( "Extracted:", extractItem( "fizz" ) );
  • console.log( cache );
  •  
  •  
  • // I remove the given key from the cache and return the value.
  • function extractItem( key ) {
  •  
  • // WARNING: Never do this (for a variety of reasons). This is just something
  • // that I am occasionally ** TEMPTED ** to do when I get super frustrated at
  • // the fact that I have to create an intermediary variable to hold the value
  • // before I delete the key from the hash. More than anything, this is just to
  • // demonstrate that the concept works... NOT that it is good.
  • try {
  •  
  • return( cache[ key ] );
  •  
  • // After we've returned the value, delete the key.
  • } finally {
  •  
  • delete( cache[ key ] );
  •  
  • }
  •  
  • }
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the extractItem() method executes the return() and then the delete(). This actually reads quite nicely, in terms of an order of operations (which is probably why it's such a temptress). And, when we run the code, you can see that the correct value is returned and the correct keys are deleted:

Extracted: bar
Extracted: buzz
Object { hello="world"}

It works, but here's why I'll never do this:

  • It falls under the "too clever" category; it's dark magic.
  • The expression of the code is not aligned with the intent of the code.

NOTE: There is probably a performance overhead as well; but, it's so marginal that I won't even consider it as a negative.

Anyway, just needed to get that off my chest. In a perfect world, the "delete" action would just return the value and then I could sleep soundly at night.




Reader Comments

So you're not happy with the delete operator. Why not create an abstraction that has the semantics you expect?

console.log('set', cache.set('foo', 'bar'));
console.log('get', cache.get('foo'));
console.log('delete', cache.delete('foo'));
console.log('delete-again', cache.delete('foo'));

Result:
set bar
get bar
delete bar
delete-again undefined

FWIW, deleting a key and returning the (previous) value at the same time violates command-query separation.

https://en.wikipedia.org/wiki/Command%E2%80%93query_separation

Reply to this Comment

@Patrick,

In the code that actually triggered this thought last night, I actually took that kind of approach. I had methods for .setItem(), .getItem(), and deleteItem() that all work like you would expect. But then, I created a subsequent method called .extractItem(), which encapsulated the intermediary-variable:

function extractItem( key ) {
. . . . var value = getItem( key );
. . . . deleteItem( key );
. . . . return( value );
}

That said, I am not sure that it necessarily violates Command/Query separation. I mean, in the purest sense, I guess so. But, the intent of the method is perform a specific mutation. As such, the "side effect" isn't really a side-effect, it's the thing you're trying to do. But, just a thought.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.