pop·cy·cli·cal

Tuesday, March 23, 2010

Programming Language Misuse

I’m feeling a bit guilty about some code I wrote:

using (new OperationTimer("MyOperation", this))
{
    // ... complete operation
}

This innocent looking C# snippet is hiding a tricky secret - the using statement is being misused (no pun intended).  The documentation defines the intended usage clearly:

using Statement
Defines a scope, outside of which an object or objects will be disposed.

The problem?  The notion of “object disposal” is being hijacked!  In your garden variety IDisposable implementation, you’d be dealing with an external resource that needs to be released before the object can be removed from memory.  Instead, I’m using it to time a block of code like so:

class OperationTimer : IDisposable
{
    private readonly string _operationName;
    private readonly ITimable _obj;
    private readonly Stopwatch _stopwatch;

    public OperationTimer(string operationName, ITimable obj)
    {
        _operationName = operationName;
        _obj = obj;
        _stopwatch = new Stopwatch();
        _stopwatch.Start();
    }

    public void Dispose()
    {
        _stopwatch.Stop();
        _obj.OnOperationCompleted(_operationName, _stopwatch.Elapsed);
    }
}

The constructor starts a timer and the Dispose() method stops it and reports the elapsed time.  (aside: if you’re interested in how I’m using the timer, check out my previous article Simplified Performance Counters) There are certainly other ways to accomplish this same behavior, but they lack the elegance of a neatly scoped code block.  It’s arguably an acceptable way to repurpose the language.  In fact, the ASP.NET MVC authors saw fit to use it in a similar fashion with the BeginForm helper.  The only “resource” it disposes of is to render a closing </form> tag.

My question is: When does repurposing language constructs turn from “acceptable language use” to a “dirty trick”, or worse, “illegible line noise”?

It seems like a slippery slope.  One instance that I don’t care for is controlling execution flow by-way-of logical operator precedence in most C-like languages:

expression1 && expression2 || expression3

Which is equivalent to:

if (expression1)
    expression2
else
    expression3

This takes advantage of the order of evaluation in a logical statement – it is assumed (correctly) that expression2 will never be evaluated if expression1 is evaluated as false, and instead, expression3 will get to run.  Likewise, if the first two evaluate to true, the truth value is known for the statement and expression3 is never evaluated. This is clearly not the intended usage which the language designers had in mind, but it works, and it saves any keywords from being written.

Some truly beautiful code has been written by way of hijacking the language.  For instance, here’s a program that will calculate the value of pi using an ascii circle.  Truly neat - but also completely useless from a software development standpoint.

What do you think?  Should I just get over my guilt about repurposing IDisposable?  Or, should I be true to the original intent of the language and find another way?

Wednesday, 24 March 2010 07:49:09 (Eastern Standard Time, UTC-05:00)
As long as it makes the code more readable, I don't really see a problem with it. As you said, it's not like there aren't existing APIs that have exploited the using statement similarly. I suppose you could achieve the same thing by passing around an Action, but that's actually *less* readable IMO than the using-based approach.
Wednesday, 24 March 2010 07:52:20 (Eastern Standard Time, UTC-05:00)
On the "misuse scale", I'd put this somewhere in the middle. It's not 100% to the original intent, but it's not a brute force hack. In a way it's elegant, in another way it kinda hides what's going on.

The measuring stick I use for cases like this is, "Is it obvious?". Code should be obvious, as much as reasonably possible. The costliest part of software is maintenance. I always try to ask myself, "If I come back to this code in six months, or worse yet, a new employee does, will they know what's going on without asking me? Will they (or myself) have to dig and dig to find out what's really going on?". Ideally, as you dig deeper into code you find out more details about what's happening, sure, but you don't find weird side-effects or random artifacts being produced. You should be able read your top-level module and know everything that is happening, even if you don't know exactly how it's doing it all. Once again, this is ideally, and I know that isn't always cost-effective at the time.

All that being said - I would ask how much you're gaining by doing it that way? Personally, I'd ditch the using statement, and just have the OperationTimer called manually. It does the same code, and is more readable and transparent at the end of the day. Something like:

var timer = new OperationTimer("whatever", this);

//do whatever

timer.Finish();

It adds no LOC, and is very obvious what's going on.

And I totally agree on abusing order-of-operations like that. The second example is much more readable and obvious of what's going on. If there's a bug, the second one also shows "original intent" a lot clearer, instead of just wondering "Huh, I wonder if they knew how these would short-circuit or not".


(note: You have a typo. Your constructor says "OperationWrapper").
Wednesday, 24 March 2010 11:06:16 (Eastern Standard Time, UTC-05:00)
Thanks for the comments. Yeah, passing an Action might look like:

OperationTimer.Time("Operation Name", () => { /* ... do some operation */ });

...which, in C#, gets to get a bit syntax heavy. I kind of wish there were a shortcut for parameter-less lambda consisting of only the braces. I guess that might interfere with normal code blocks, perhaps.

@Rob - typo fixed, thanks. The piece that using the procedurally-explicit "StartTiming(); /* ... do some operation */ EndTiming();" calls loses is open-close idiom you get implicitly in the language with the braces. It's possible to get sloppy and forget to end the operation if you have to make another call, but you'll never forget when the program won't compile without closing the brace.

This kind of dovetails with functional programming and DSLs a bit, I suppose - the idea that you should be able to define (or extend) language constructs that can be used to solve the given problem using a grammar that's natural to the domain.
Comments are closed.