Tuesday, December 11, 2007

Beautiful Code vs. Readable Code



For many years--decades actually--I was a big fan of beautiful code. I read almost everything by Brian Kernighan, Jon Bentley, and P. J. Plauger. This passion for elegant code was an attempt to re-create the rush I felt when I first read:

*x++ = *y++

in the C Programming Language. I'd never seen anything so beautifully succinct. It was luminous!

But as years passed, I read many clever algorithms, many impressive optimizations, many small tricks. And I got less and less charge from each of these discoveries. The reason, quite frankly, is that they almost always fell into one of two categories: some very elegant expressiveness in a new language (Ruby converts from Java can attest to this) or a technique that I'm not likely to ever use. In other words, I was chasing baubles.

In time, my esthetic sense turned to code clarity for its jollies. Today, if I can pick up a blob of complex code, read it in one pass, and accurately understand what it's doing; then I feel the rush again. I most often have this feeling when reading the code of great, non-academic developers. To be honest, when reveling in such moments, I frequently have the perception that my code is not like theirs. Even my best code doesn't quite snap together like theirs does. And I have wondered what I could do to improve my code clarity.

Kent Beck's new book, Implementation Patterns is a short handbook on code clarity. I have read much of it and already I recognize some bad habits that undermine my code's readability. Beck basically looks at typical coding issues and dispenses sage advice.

This means that some recommendations are best suited to beginners, while there are just enough of the other more subtle suggestions to keep the attention of a veteran who cares about clarity. For example, one poor habit I have developed without thinking about it too much is mixing levels of abstraction in the same method. So, for example (using Beck's example):

void process() {
input();
count++;
output();
}

Here the second statement is clearly at a different level of abstraction than the other two, which makes the code harder to read quickly. Beck proposes the following, which I agree is clearer.

void process() {
input();
tally();
output();
}

There are many other habits of mine that this book has illuminated. And in the half a dozen changes it will bring to my style, I think I have derived more benefit than in all the essays I've read on beautiful code.

Before leaving off, however, I should point out two caveats. The beginner-to-intermediate material dominates; so you'll need to skim over large parts of the text to extract the valuable nuggets. (However, this aspect makes it a great gift for junior programmers at your site.) A second point is that the book lacks for good editing. A book on code clarity should be pellucid; this one is not. (Consider the use of the word 'patterns,' which is highly misleading. It's not at all about patterns.) But these are forgivable issues. The book is a useful read if you share my appreciation of clear code.

13 comments:

  1. If you like *x++ = *y++ you should check out Haskell, the unchallenged lord and master of the one-liners. I forget the link, but if you google hard enough, there's a source-character-count-optimized version of the four queens problem written in just four lines of code. And one of those lines is actually just the IO code to print out the solution. It's quite amazing.

    But clever code is never as productive or useful as readable code. It's analogous to in mathematics where authors will name their variables with greek letters (or sometimes even more obscure letters) just to sound more fancy and intelligent. Fancy is fancy, but readable is better for the world.

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. Anonymous9:49 AM

    I think i would rather use count_up(); than tally(); but I agree about the concept.

    One should never forget that ruby and python are easier than Java / C / C++ (which is not necessarily a bad thing, more people means more ideas) and this will directly impact the thinking pattern (if you have to make the parser happy, or if you can think of a problem at hand)


    PS: I find it hilarious that there are so many C coders that use perl a lot. I think they are lazy to never try python or ruby, but instead keep on bitching against them. I know a few of these guys. For them, ruby/python is EVIL and they stick to their perl mindset. :>

    ReplyDelete
  4. Anonymous10:16 AM

    I also admire Kernighan, et al.'s powerful and beautiful coding style, and am always glad to hear of more books in a similar vein. However, I thought the comment:
    (Consider the use of the word 'patterns,' which is highly misleading. It's not at all about patterns.)
    was comically ironic. While I was reading the post, I was asking myself, "When's he going to talk about, O'Reilly's 2007 book 'Beautiful Code'? That's what the title's about, right?" (Reads more.) "Uh, right?" (Finishes article.) "Gee, that title's kinda misleading." :-)

    ReplyDelete
  5. Anonymous11:58 AM

    I disagree with using tally() instead of count++. tally() will require readers to locate the tally() code to determine what tally() actually does. The extra time spent searching for the relevant code certainly isn't worth retaining some level of abstraction.

    Of course, this wouldn't apply to significantly more complex code segments than count++.

    A simpler answer: decent commenting.

    ReplyDelete
  6. @anonymous re commenting:

    The name "tally" is a comment. The best form of commenting is the code itself. If the reader can't tell what tally() does, then it needs a better name, like "update_counter", "next_event", or whatever makes sense in that context.

    Also, Mike Vanier uses the phrase "snap together" to describe Haskell in

    Scalable computer programming languages
    .

    But you don't need Haskell to write clean code. The secret is to
    design each function to do one and only one thing, and avoid side effects like I/O and globals. Then you can put functions together like legos and each function always works the same.

    ReplyDelete
  7. Anonymous3:32 PM

    beauty is only skin deep. Error handling is ugly to the bone!

    ReplyDelete
  8. Anonymous3:36 PM

    Proper credit should be give to IBM, who came up with: Input(); Process();Output();...if you don't believe this, google for IBM and HIPO

    ReplyDelete
  9. More readable == easier to read. It's clearly easier to determine what count++ does than what tally() does. "similar levels of abstraction" is useful only when it makes for "easier to read".

    ReplyDelete
  10. Anonymous8:43 PM

    I just want to give another +1 to Haskell when it comes to clean code. There's a caveat: Haskell doesn't shy away from extremely high levels of abstraction which can be difficult while you're learning, but the end result is a language which makes it almost impossible to write slushy, spaghetti code.

    ReplyDelete
  11. Anyone interested in this topic needs to read Kernighan & Plauger, The Elements of Programming Style.

    ReplyDelete
  12. Anonymous8:14 AM

    Sorry, it's been some time i did some programs in C but you make me wonder what exactly this line does: *x++ = *y++

    ReplyDelete
  13. Pablo, *x++ = *y++ means copy the memory content where y points to into memory where x points to. Then increments x so it points to the next memory location, and do the same with y.

    ReplyDelete