The Artima Developer Community
Sponsored Link

Weblogs Forum
What Do You Consider "Readable" Code?

34 replies on 3 pages. Most recent reply: Apr 15, 2009 1:24 PM by Marty Fried

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 34 replies on 3 pages [ 1 2 3 | » ]
Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

What Do You Consider "Readable" Code? (View in Weblogs)
Posted: Mar 18, 2009 6:12 PM
Reply to this message Reply
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?


Ronald Tetsuo Miura

Posts: 22
Nickname: ronaldtm
Registered: Jan, 2004

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 7:49 PM
Reply to this message Reply
Would you consider Perl's one-liners as readable code? :)

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 7:54 PM
Reply to this message Reply
> Would you consider Perl's one-liners as readable code? :)
>
I'd say that depends on what the line of code says. This is fairly readable Perl:


print "Hello World\n";

Ronald Tetsuo Miura

Posts: 22
Nickname: ronaldtm
Registered: Jan, 2004

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 8:42 PM
Reply to this message Reply
So, it has nothing to do with the number of lines, but with their clarity?

'Concise' is good.

'Abstraction' is good until it leaks.

'Clarity of intention' may be good to understand what the code should do, but if it blurs too much what the code really does, it is just bad.

And, 'readable code' certainly is not the same as 'code that reads just like English'.

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 9:19 PM
Reply to this message Reply
> So, it has nothing to do with the number of lines, but
> with their clarity?
>
I'd say it has to do both with the number of lines and with their clarity.

Morgan Conrad

Posts: 307
Nickname: miata71
Registered: Mar, 2006

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 11:14 PM
Reply to this message Reply
In the Scala example, "intercept" does several things.

1) if there is no exception, fail()
2) if there is an exception, check that it is of the declared type, else fail()
3) return the exception

This is arguably "too much". I'm o.k. with it, but I really really don't like the name. Shouldn't it be something like "requireException" or "failIfNoException"???

Also, I don't see the readability of unit test code as the proper question. The readability of real code is what matters.

Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 11:27 PM
Reply to this message Reply
It is interesting to point out the thoughts of Paul Graham
about the importance of brevity and concision:
http://www.paulgraham.com/arcll1.html

I myself favor concision. I think readability of
intent is more important than readability of the implementation (otherwise we will be all programming
in Assembly). Of course, concision does not mean
the way of APL/J/K or Perl. Concision means using
a non-verbose non-redundant language and creating
a DSL when needed.

Bill Venners

Posts: 2284
Nickname: bv
Registered: Jan, 2002

Re: What Do You Consider "Readable" Code? Posted: Mar 18, 2009 11:32 PM
Reply to this message Reply
> In the Scala example, "intercept" does several things.
>
> 1) if there is no exception, fail()
> 2) if there is an exception, check that it is of the
> declared type, else fail()
> 3) return the exception
>
> This is arguably "too much". I'm o.k. with it, but I
> really really don't like the name. Shouldn't it be
> something like "requireException" or
> "failIfNoException"???
>
Groovy's easyb has a similar construct called ensureThrows, but it doesn't return the exception. That name is clearer to the uninitiated than intercept, but longer to type. I did consider expectException, but the trouble is that it is also longer and "Exception" would usually be repeated, because it usually shows up in the exception type name. For example your requireException suggestion would end up looking like:

requireException[IllegalArgumentException] {
// ...
}

Note "Exception" is repeated. I realize intercept will be a bit obscure to newcomers, but I think it will be easy to learn, remember, type, and read. And it does map to an interception in American football. It catches the ball and runs it back and hands it to you. Not a perfect metaphor but that's where it came from.

> Also, I don't see the readability of unit test code as the
> proper question. The readability of real code is what
> matters.
>
I would disagree. I would think the readability of test code matters as much as production code, because when a test fails you need to go figure it out. But I mainly used unit testing for the examples because it is a domain everyone should be familiar with. The ideas should apply to code in any domain, not just testing.

Joao Pedrosa

Posts: 114
Nickname: dewd
Registered: Dec, 2005

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 12:34 AM
Reply to this message Reply
I like Ruby's conciseness, even though I don't share the ideals of other programmers when they create their APIs. There are times I might have to take a step back to try to find out what a certain piece of Ruby code is doing, because I might not be used to certain idioms other folks love to make use of.

I am glad you all have been able to reach this point of discussion as a community. Certain tradeoffs go unnoticed by folks who are not exposed to different viewpoints.

Exactly because of things like the ones discussed in this article is because I think that while Java was and is widely popular, I fear C# will continue to steal Java's thunder, because C# has striven to keep things more concise than Java. It's strange how slow Microsoft can be to update its Windows systems, but with .NET and C# Microsoft has at times being "too fast" in developing new features in the eyes of many people. Although I think in the end it will prove to have been the right thing, and one of the main ways Microsoft managed to modernize their Windows systems.

Back to the article, I have been exceedingly happy whenever I wanted to extend my Ruby programs to make things happen in fewer lines or that each line did more than just one thing at a time, which has helped with things like "security" of the line, with decreasing the chance of missing something when coding anything however unimportant like closing IO streaming, with saving creating too many supporting methods in order to keep the lines for each method under control...

But once getting used to how Ruby works, it becomes just too painful to use Java to create any thing really. For example, I have extended NetBeans with a few modules to make it more adapted to editing text files the way I have gotten used to. When using GUI drag and drop in NetBeans, I have not managed to make the designer do all that I wanted to do. Also, the Swing table does things a little speedier than similar controls of other GUI toolkits such as GTK and HTML if you will, but does not do all the same things by having different or "fewer" features. Collectively, Java's nuisances have seemed like too hard to get ridden of, because the way Java works by default is not the most user-friendly or end-developer friendly and it can be difficult to bend it all the way to make it easier, when compared to what Ruby provides us with out of the box.

JRuby is good. But there was a conflict when trying to use it to extend NetBeans because NetBeans uses some parts of JRuby to do the Ruby NetBeans modules. Also, JRuby can be much slower when starting up while loading lots of Ruby libraries than CRuby. Think things taking several seconds when CRuby takes less than a second. This "lag" makes me believe that perhaps another language like Ruby but more adapted to loading lots of files fast could end up being more appreciated in environments such as Java and .NET. While JavaScript could be that language, it lacks some features and standard libraries.

May I add that I prefer writing concise Ruby code than concise JavaScript code. While JavaScript is plenty malleable, it too can be hard to understand when developers play with its internals. On the plus side, JavaScript is everywhere...

With all that said, when there are two or more Ruby libraries are targeting the same end-goal, a little more conciseness or clarity in one won't necessarily make it more popular, because the competition is fierce and being at the right place and at the right time, such as being included in the main distribution's set of libraries can guarantee a lot of traction and improvements down the line anyway. :-)

I think that there is a limit to the "conciseness ad absurdum" somewhere. Albeit folks should not arbitrarily try to define this limit because depending on the language, on the end-goal, on the users, who knows where it is?

Vincent O'Sullivan

Posts: 724
Nickname: vincent
Registered: Nov, 2002

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 3:01 AM
Reply to this message Reply
> > So, it has nothing to do with the number of lines, but
> > with their clarity?
> >
> I'd say it has to do both with the number of lines and
> with their clarity.

Readability is not just a function of what is written. It also has to do with the language experience (for want of a better word) of both the reader and writer.

Code written by a (competent) learner is often identifiable by the high level of comments combined with the verbose detail of the code itself. With experience the number of comments decrease as does the amount of code required to do any given task. All the time, the author subconsciously assumes that the reader has the same level of language competence (because we all, basically, write for ourselves). Thus there can be a mis-match, both when a beginner reads code by an experienced author or also when an experienced writer reads the work of a beginner.

Gerald Loeffler

Posts: 5
Nickname: gloeffler
Registered: Dec, 2008

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 3:45 AM
Reply to this message Reply
I certainly agree with the basic tenet that less code is better. I would add that it is unnecessary (from the perspective of a human programmer) and redundant code that we want to eliminate. However, i feel i'm missing a few aspects in this discussion (the text by Paul Graham that was quoted in this thread touches on some of these ideas):

1. syntactic verbosity: semi-colons to terminate lines; curly braces to identify blocks; the .class-syntax to identify a thrown exception: Scala optimises away some of that compared to Java. But i think part of learning a language is training your brain to deal with this fluff automatically, so i don't think it distracts and i don't think it enters the "amount of code" measure that we try to minimise.

2. brevity because the language hides essential detail from you: variable definition/initialisation with type-inference; Scala implicits and extractors: i for one can't write or understand programs without knowing the types of all my variables, if/what implicit conversions are applied, or if/what extractors are used in my Scala pattern matching expressions - to name just a few examples. It's just that Scala hides some of these essential details from me, in a way that is less obvious and straighforward than e.g. a method hiding it's implementation details. So most of the time my mind has to add that hidden detail (e.g. by determining the type of an initialisation expression to know what type a variable is; or by hunting for implicits). These are extremely powerful features, and they reduce the amount of code significantly, but they directly translate into cognitive effort on the part of the programmer to fill in the missing details. (This is also where/why tool support is really essential in Scala, even more so than in Java.)

3. brevity through abstraction: this allows us to express business intent (e.g.: debit this account; send this message to the general ledger) rather than coding intent (e.g.: i want this exception to be thrown; call this method) and obviously Scala adds functions as an additional abstraction vehicle over the classes available in Java. I think this is what matters most as it allows us to layer abstraction over abstraction and thereby write code that is both concise and expressive and hence readable in its entirety (rather than just at the local level). It seems to me that this is readability that deals with the "what" rather than the "how" (in keeping with the good design practice to model interfaces after intent rather than implementation).

just some thoughts...,
gerald

James Iry

Posts: 85
Nickname: jiry
Registered: Nov, 2007

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 7:40 AM
Reply to this message Reply
No free lunches. At one extreme is assembly. It lets you see in exquisite, gory detail exactly how things are being done. But what is being done can be very hard to fish out. At the other extreme are thing like constraint logic languages. The how can be a bit of mystery, the what is crystal clear (at least once you understand the language and its idioms).

Scala settles for a range that I don't think this article acknowledges. You can "write Java in Scala" - so you can limit yourself to exactly the abstraction level that works in Java and get exactly the same trade-offs. Or you can write at a significantly higher level, with its own set of trade-offs.

//Java

List<Person> minors = new ArrayList<Person>();
List<Person> adulst = new ArrayList<Person>();

for(Person person : people) {
if (person.age() < 18) {
minors.add(person);
} else {
adults.add(person);
}
}

//Java written in Scala

val minors : List[Person] = new ArrayList
val adults : List[Person] = new ArrayList

for(person <- people) {
if (person.age < 18) {
minors add person
} else {
adults add person
}
}

//Scala, high level

val (minors, adults) = people partition (_.age < 18)


"A programming language is low level when its programs require attention to the irrelevant."
- Alan Perlis

John Bayko

Posts: 13
Nickname: tau
Registered: Mar, 2005

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 8:19 AM
Reply to this message Reply
A while ago I coded the exact same routine in Python and Java - basically, input characters until a terminating sequence (which could be escaped) was read. The sequence always ended with CR LF, so I could do this in Python line by line, just checking the end characters (with a special case if the sequence itself included CR LF). In Java, in the end it was actually simpler to compile a state machine in code and read the input character by character (using the standard buffered input classes).

It wasn't that I couldn't actually do the same thing in Java line by line, but that it has so much more low-level support that not using it just felt like a waste of efficiency. And really, doing it line by line wasn't really much less code.

Maybe it's just me, but I think there is a desire for programmers to go to the lowest level available (or convenient) so that you know you're getting the best result for your effort - until you feel your returns are not worth it any more, which is somewhat subjective. When I'm using Python, I really think differently than I do with C, partly because of what I can't do in Python as well as what I can.

Vijay Kandy

Posts: 37
Nickname: vijaykandy
Registered: Jan, 2007

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 10:30 AM
Reply to this message Reply
Can’t you write significantly higher level code with a good library that abstracts the details?

// Java written in Java using google collections

List<Person> minors = Collections2.filter(people, isMinorPredicate);
List<Person> adults = Collections2.filter(people, isAdultPredicate);

I think 1 liners are just facades for 1K liners.

Also, I am not a language expert but I wouldn’t like if a language had 1000 built-in features. I prefer if it allowed me to create a 1000 features by letting me “extend” some simple and basic ones.

Carson Gross

Posts: 153
Nickname: cgross
Registered: Oct, 2006

Re: What Do You Consider "Readable" Code? Posted: Mar 19, 2009 11:10 AM
Reply to this message Reply
> Can’t you write significantly higher level code with a
> good library that abstracts the details?
>
> // Java written in Java using google collections
>

> List<Person> minors = Collections2.filter(people,
> isMinorPredicate);
> List<Person> adults = Collections2.filter(people,
> isAdultPredicate);
>

> I think 1 liners are just facades for 1K liners.
>
> Also, I am not a language expert but I wouldn’t like if a
> language had 1000 built-in features. I prefer if it
> allowed me to create a 1000 features by letting me
> “extend” some simple and basic ones.

That's just squeezing the balloon, forcing you to define isMinorPredicate and isAdultPredicate classes elsewhere. In fact, you'll end up with more code when you do it that way, which will make your system even more lumbering and obese. You need the expressiveness of something like closures to truly reduce the code. Here's what it looks like in GScript:


var minors = people.where( \ p -> p.Age < 18 )
var adults = people.where( \ p -> p.Age >= 18 )


(Yeah, I know it's doing the loop twice. I'd leave it that way unless this were really critical code, in the name of making the code easy to read.)

The calculation, in my opinion, is: how much syntax is a particular concept worth. Something like "A person's age is less than 18" is probably not worth very much, and closures work great in that context. Something like "Manages a connection and reports statistics on it's use" is probably worth a class.

Cheers,
Carson

Flat View: This topic has 34 replies on 3 pages [ 1  2  3 | » ]
Topic: What I learned at Java Posse Roundup '09 Previous Topic   Next Topic Topic: Mixins considered harmful/1

Sponsored Links



Google
  Web Artima.com   

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