The Artima Developer Community
Sponsored Link

Angle Brackets and Curly Braces
What Do You Consider "Readable" Code?
by Bill Venners
March 19, 2009
Summary
I've observed two, often competing aspects of code readability: clarity of the programmer's intent (the "what") and clarity of the implementation (the "how"). What does code readability mean to you, and what do you think is the best way to maximize it?

Advertisement

In the discussion of Bruce Eckel's weblog post, The Positive Legacy of C++ and Java, Noel Grandin expressed the sentiment that languages like Ruby and Scala are biased heavily in favor of writing code rather than reading code. The observation was that although Java is verbose, it is easy to figure out what some random bit of Java code is doing.

I agree with the latter part of the statement: although Java code may be verbose compared to Ruby or Scala, that verbosity can help readers of code see more easily what the code is doing. However I would say the first part differently. I don't think Ruby and Scala are biased towards writing code. These languages do offer tools to help make code readable, but readable in a different way than Java. My observation is that the readability strategy of Java is to reveal how the code works, whereas the readability strategy of Ruby and Scala is to reveal the programmer's intent.

There's a downside to both of these strategies. For Java, sometimes the "how" can be so verbose as to overwhelm the reader, making it harder to see the "what": i.e., the overall intent of the programmer. This is a classic case of not being able to see the forest for the trees. For Scala and Ruby, the downside is that although their more concise code can reveal programmer intent with greater clarity, how that intent actually gets implemented can be less clear.

For a quick example of what I mean, compare how you check a bit of code throws an expected exception in JUnit (Java) and ScalaTest (Scala). In JUnit 3, you do this:

try {
    "hi".charAt(-1);
    fail();
}
catch (StringIndexOutOfBoundsException e) {
    // Expected
}

You open a try block, write the code you expects will throw an exception, call fail, then close the try block and create a catch clause for the expected exception. You need not put anything between the curly braces of that catch clause, but I always included an "Expected" comment to make it more obvious it was my intent to swallow the exception. In JUnit 4 this can be expressed slightly more concisely using an annotation:

@Test(expected = StringIndexOutOfBoundsException.class) 
public void charAtTest() {
    "hi".charAt(-1);
}

Two problems with JUnit 4's annotation approach are 1) you must create a new test method for each bit of code you want to ensure throws an expected exception, which adds some new verbosity compared to the JUnit 3 approach. And 2) you can't check anything on that exception afterwards. For example, to check the detail message of the thrown exception, you'd need to use an explicit try/catch even in JUnit 4:

try {
    "hi".charAt(-1);
    fail();
}
catch (StringIndexOutOfBoundsException e) {
    assertEquals(e.getMessage(), "String index out of range: -1");
}

By contrast, here's how you check a bit of code throws an expected exception in ScalaTest:

intercept[StringIndexOutOfBoundsException] {
  "hi".charAt(-1)
}

You write intercept, then in square brackets provide the name of the exception you're expecting, then in curly braces the code you expect will throw that exception. That's it. Note that there's really no clutter here other than what is needed to express the programmer's intent. There's a "keyword": intercept, the name of the exception type: StringIndexOutOfBoundsException, and the code under test: "hi".charAt(-1).

One reason it is called intercept is because this construct doesn't just catch the exception, but also returns it. So if you want to check something inside the caught exception, you can do that in ScalaTest like this:

val caught = intercept[StringIndexOutOfBoundsException] {
  "hi".charAt(-1)
}
assert(caught.getMessage === "String index out of range: -1")

Again here, there's not much clutter besides what's needed to express the programmer's intent.

In my mind, Scala and Ruby are higher level languages than Java, in the same way (though not to the same extent) that Java is higher level than assembly. Scala and Ruby allow you to more easily abstract away detail than Java, and this is what the greater conciseness of these languages represents. Because you can work at a higher level, you need fewer lines of code in Scala or Ruby to express the same intent as in Java.

I think it is a fairly well accepted tenet that fewer lines of code is generally better, though I'm not sure to what extent this has been proven by actual measurement. "Smalltalk" Dave Thomas once put the sentiment this way:

Klocs Kill - the amount of code you ships becomes the tax on future development.

In case you're not familiar with the acroynm, 1 KLOC (pronounced kaylock) is 1000 lines of code. The claim here is that the maintenance cost of a piece of software has an important relationship to simply the bulk of that code. The greater the bulk, the greater the cost. And it seems to follow that if a particular language allows you to express more intent with less code than another, it should generally be cheaper and quicker to write and maintain a program in that language. That's a big, unscientific leap, but I have heard many anecdotes from people who claim this is their experience. My guess is that code readability plays some part in this difference. If fewer lines of code can help you more quickly understand what some code is doing, that should help you move faster.

The trouble is this seeming tradeoff between seeing the intent and understanding how that intent is implemented. For example, although the intent of a ScalaTest intercept call may be quicker to ascertain than one of JUnit's slightly more verbose alternatives, it is less obvious how ScalaTest's intercept actually works than the JUnit approaches. If you're well versed in Scala, then I think you will be able to guess how it works, because there's really only one way it could work. But if you're not familiar with Scala, you probably won't be able to guess how it works.

The comment by Noel Grandin that inspired me to write this blog post suggests that not being able to figure out how a bit of code works is harmful to productivity. I think that's true only when you need to figure out how something works, and most of the time you don't. If you think about it, Java code itself does not make it clear what the bytecodes are doing. And if you look at the bytecodes with javap, it's not clear what a JIT compiler might do to them at runtime in some random JVM. Nevertheless, most of the time you don't care about these details. So long as the lower levels function properly, you can safely abstract them away and work at a higher level. I think that's also true in the case of intercept. It should always work, and if it doesn't, you can just go look at its implementation to see how it is working. So long as there's a way to dive down into the lower level details when needed, I think you'll get higher productivity with higher-level, more concise code.

But given I have no proper scientific evidence, I'm curious what others think. How do you think the tradeoff between clarity of intent and clarity of implementation works out? What do you think makes for the most readable code? And what do you try to do, in whatever language you're using, to maximize the readability of the code you write?

Talk Back!

Have an opinion? Readers have already posted 34 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Bill Venners adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Bill Venners is president of Artima, Inc., publisher of Artima Developer (www.artima.com). He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Active in the Jini Community since its inception, Bill led the Jini Community's ServiceUI project, whose ServiceUI API became the de facto standard way to associate user interfaces to Jini services. Bill is also the lead developer and designer of ScalaTest, an open source testing tool for Scala and Java developers, and coauthor with Martin Odersky and Lex Spoon of the book, Programming in Scala.

This weblog entry is Copyright © 2009 Bill Venners. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use