Aesthetics... what can you do?
When I was a younger programmer, I felt guilty about being a neat-freak. It was a selective obsession. There I was, sitting in my office with stacks of papers and books covering every available space, but I was oblivious to it. As long as my code looked great, I could shut out the chaos around me: the towering stacks of dead tree that could've toppled and crushed me if I had sneezed.
I don't know why I felt bad about being that way, but in retrospect, I think that I felt that I was wasting time caring about neatness in code. It took me a couple of years to figure out that the ergonomics of code matter, and that the time you spend decluttering your code can declutter your thoughts as well.
Because of this personal history, I've been a bit sensitive about the "real code" vs. "example code" distinction. A few days ago, Bryan Cantrill blogged about Brian Kernighan's example in the Beautiful Code book:
While I confess that I like writing a neatly recursive routine, I also find that I frequently end up having to unroll the recursion when I discover that I must deal with data structures that are bigger than I anticipated -- and that my beautiful code is resulting (or can result) in a stack overflow. (Indeed, I spent several unpleasant days last week doing exactly this when I discovered that pathologically bad input could cause blown stacks in some software that I'm working on.)
To take a concrete example, Brian Kernighan has a great chapter in Beautiful Code about some tight, crisp code written by Rob Pike to perform basic globbing. And the code is indeed beautiful. But it's also (at least in a way) busted: it overflows the stack on some categories of bad input. Admittedly, one is talking about very bad input here -- strings that consist of hundreds of thousands of stars in this case -- but this highlights exactly the problem I have with recursion: it leaves you with edge conditions that on the one hand really are edge conditions (deeply pathological input), but with a failure mode (a stack overflow) that's just too nasty to ignore.Now, there are ways to deal with this. If one can stomach it, the simplest way to deal with this is to setup a sigaltstack and then siglongjmp out of a SIGSEGV/SIGBUS signal handler. You have to be very careful about doing this: the signal handler should look at the si_addr field in the siginfo and comparing it to the stack bounds to confirm that it's a stack overflow, lest it end up siglongjmp'ing out of a non-recursion induced SIGSEGV (which, needless to say, would make a bad problem much worse). While an alternative signal stack solution may sound hideous to some, at least the recursion doesn't have to go under the knife in this approach. If having a SIGSEGV handler to catch this condition feels uncomfortably brittle (as well it might), or if one's state cannot be neatly unwound after an arbitrary siglongjmp (as well it might not), the code will have to change: either a depth counter will have to be passed down and failure propagated when depth exceeds a reasonable maximum, or the recursion will have to be unrolled into iteration. For most aesthetic senses, none of these options is going to make the code more beautiful -- but they will make it indisputably more correct.
Bryan's blog awakens all of those old feelings in me. Is it wrong to appreciate the beauty of Pike's recursive algorithm? To want to preserve it as unsullied as possible in production code? Or is that a trap that beautiful code can lead us into?
What do you think?

Comments (1)
Is it a trap though? Why can the code not be tail call optimized? Bryan has made the comment in the comments of the article you linked to saying that the code can not be tail call optimized. I do not currently see the reason why not and have asked him to clarify the issue.
Posted by PJW | July 17, 2007 11:30 PM