Making code more readable, that is, easier to read and therefor easier to grasp, has always been an important secondary goal for me when writing code. The primary goal is correctly working software, but immediately after the code works, it enters maintainance mode. Refactoring is a great tool to improve the structure and accessibility of existing code, but it doesn’t necessarily lead to code that is more readable. I’ve even found that there are multiple levels of “easily accessible” code, depending on your experience with different code structures. But that’s another topic for another blog post.
Readable code
Before I can talk about how to create readable code, I have to define what “readable” means to me: I see readable code as code everybody can read (out loud) and directly understand without further reference.
Here’s an example of a little code snippet in Java that follows my definition:
ForeachFile.in(directory).checkIf(IsOlder.than(5).days());
If you replace the parentheses and dots with whitespace, you can read the line fluently and gain a proper idea of what it is doing.
I’ve always found it much easier to write code similar to this example in dynamic languages. In Groovy, Scala or Perl, you are used to invent your own domain specific language dialect that’s much more readable and concise than using the underlying API directly with all the tedious details. But with a bit of practice, Java (and other statically typed languages) are nearly as flexible to reach (or get near) the highest level of readability: code in natural language.
Start with a sentence
The easy way to accomplish the really challenging task of matching computer programming language and naturally spoken language is to pass it on to the compiler. Start with the desired behaviour of a line of code written as a sentence. The compiler will raise all kinds of objections against this form of programming, and all you have to do is to follow the compiler’s complaints, add some special characters and camel casing and then fill out the classes and methods you just planned ahead.
In reality, it will not be as easy as outlined above, but the process stays the same:
- Write your desired code, neglecting all compiler errors
- Identify method calls, method parameters, class names and other language features as it fits best
- Outline the next code you’ll have to write by silencing the compiler with code stubs (use the code generation features of your IDE)
- Fill out the (empty) spots you just created, starting with point 1.
Your first attempts might not be as successful as hoped, so you have to backtrack and adjust for perfectly fluent code to a slightly less perfect form, but that’s just reasonable. You still came up with the possibly most readable code you were able to write.
Know when to stop
Although the process seems to be indefinitely repeatable as you descend deeper and deeper in your code (assuming you started with rather high-level code), there will be a fine line when you have to stop the process because the technical aspects of your code will overwhelm every attempt to wrap natural language around it. You probably still have a good amount of perfectly readable code that even non-programmers can grasp at first sight. Just if you dig deeper into its details, the readability will fade.
Your code will be partitioned into two regions: One region is meant to be read, understood and adjusted if requirements change. The other part of the code isn’t as readable and exists mostly to support the first type of code. This is where you still have to be a programmer to make a change. I assume that your partitioning will meander on the border between business requirements and technical implementation.
Observations along the way
My experimentation phase with this kind of programming revealed some insights that mostly other developers made intuitively when exposed to this style in pair programming sessions.
The most interesting revelation was that the names of my classes change: Instead of using nouns, I tend to use verbs in combination with prepositions (like CheckThat, CreateSome or WaitUntil). This is unfamiliar when reading the class name in isolation, but won’t bother you if you read it in the context of the use case.
Which brings me to the next revelation: The resulting code from the abovementioned process seems to be highly focussed on the current use case. It’s not that it isn’t modifiable or inflexible, but it will serve the task at hand in the best way and fall somewhat short for other use cases. It’s in the ability of the developer to refactor the code once additional use cases appear.
Due to the structuring the natural language imposes on the code, refactorings seem to have a “scope” that can verify if the change at hand is really suitable to bring the code forward. It will be very obvious if a refactoring breaks the ruling structure of the code – the readability of your code will degrade.
Another example
Here is another example of readable code written by the process described above, this time copied from an acceptance test:
station.currentPackage().withTypicalContent().send(); WaitUntilPackage.from(stationName).isProcessedWithin( Wait.LONGER).asShownOn(center().statusbar()); Wait.SHORT.perform(); assertThatFilesAreStoredInArchive(); assertThatFilesAreStoredOn(ftpSpace, with(exportName));
You can see that it aren’t always the classnames that drive the code, method names are just as important. And you can see the fitting usage of a code squiggle in the last line, a technique I often use to squeeze in the last missing pieces of fluency.
Summary
Writing readable code that can be read and understood by virtually everyone is a tough task. The programming cycle presented in this article uses the compiler’s ability to complain and the feature of modern IDE to create code stubs (named “quick fixes” or alike) to outline naturally readable code and then fill out the gaps in the best attempt. The result will be code that looks like plain english for the most important parts of the code, the translation of the business requirements. The downside is slightly unusual naming and structure in the other parts of your code.
If you have experiences with this approach to readable code, let us know about it.
Excellent! I program mostly in C++, so I don’t know to what extent I can apply this. Still I will be looking for ways to do so.
Thank you for your feedback. We are also looking to port the process to C++, if you made some progress, blog about it. We would love to read your insights.