Clean code: what is it?
A while back I helped facilitate some discussion workshops on the topic of Clean Code. Each of the discussions seemed to be predicated on a belief that readability is the most important criterion by which to assess whether code is Clean. Indeed, the groups spent a lot of time discussing ways to establish and police coding standards and suchlike. While I agree that this can be useful, I felt the discussions missed the aspects of Clean Code that I consider to be the most important.
[Disclaimer: None of what you are about to read is novel thinking. All of it has been said before by numerous great programmers. But I do think it needs to be repeated often.]
Firstly, I don't call it Clean Code. That term seems to me to encourage the view that cleanliness is something superficial, that it is all about how the code looks. Instead I call it Habitable Code, as described by Richard Gabriel:
Habitability is the characteristic of source code that enables programmers, coders, bug-fixers, and people coming to the code later in its life to understand its construction and intentions and to change it comfortably and confidently.
Habitability makes a place liveable, like home. And this is what we want in software—that developers feel at home, can place their hands on any item without having to think deeply about where it is.
So while I agree with those discussion groups that readability is incredibly important, Habitability goes deeper. To me, Habitable code is any code that can change at the same pace as the business. Small changes to business requirements (ie. within the scope of the current domain) should incur a correspondingly low cost of change from the code, whereas large swings in business requirements can expect to incur much larger development costs, because the new requirements aren't a natural fit with the current codebase or won't be able to share much existing working code.
Given that definition, it seems to me that the most important aspect of Habitable code relates to “discoverability” — and therefore to the code’s overall structure. I believe that Habitability is achieved only when the top-level design (aka architecture?) of the application is readily apparent (ie. explicit) in the code. I think the best way to achieve that is as follows:
The application is divided into a small number of modules.
Each module represents a meaningful concept in the domain; they are named such that anyone familiar with the domain would understand their purpose.
Each has a well-defined interface, again expressed in domain terminology.
The lifecycle of each module, and the relationships between the modules, are expressed declaratively in the application's entry point(s). That is, the application's entry point(s) must state in clear declarative terms how these modules are plugged together to deliver the required business value.
All coupling between the modules is obvious in the code at this level.
(If this feels like a re-statement of the 4 rules of simple design and/or structured programming, that's no coincidence: No bad names, and no unwanted coupling. I'm not inventing anything new here.)
For me these top-level modules are a combination of form and function — their names partition the application domain, and their behaviours describe the business logic as seen from 30,000 feet. And the coupling between them must be declared and explicit too.
I also believe that this definition applies recursively. Look inside any of the top-level modules and each should show the same characteristics as the overall application:
Consists of a small number of parts with names taken from the domain.
The relationships between these parts are expressed declaratively.
All coupling between the parts is obvious and declared explicitly in their code.
These sub-parts may be applications, microservices, packages, namespaces, bounded contexts, aggregates, modules, objects, functions — anything that might be considered an "encapsulation unit" or an "object". Note also that I’m not talking about a hierarchical partitioning here — the parts can be shared.
Naively this allows us to set up a kind of “refactoring algorithm”:
Divide the application into a small number of well-named modules
Remove all implicit coupling between these modules
Recurse until bored
The decomposition of any part into other parts represents a set of implementation choices, but also a set of documentation choices. I want to be able to treat each module as a black box (Page-Jones calls them “encapsulation units”), and to be able to refactor inside the bounds of that box without friction. This requires that the interface of this part be well-defined (via automated tests, for example), and that all coupling between it and its peers be explicit, obvious and understood.
It seems to me that the value gained by applying this algorithm decreases as we go down through the layers. That is, there is more value in having obvious relationships between the top-level parts than there is deeper down in the smaller parts. The application will be easier to change if the outermost layer of code is expressed simply and in domain terms; and having low-level modules expressed cleanly and simply will have great benefit, but less overall benefit on the habitability of the application.
Another way to express this might be to say that I am advocating message-oriented programming at all scales: I care about the partitioning into modules only to the extent that the communication between them must be simple and self-evident. The encapsulation units exist only in order to hide details of how one layer of messages and state change actually "works". I guess it is also no coincidence that Michael Feathers' Naked CRC technique is often a great way to communicate designs that are habitable in this way.
All of which is a long-winded way of saying that I prefer the notion of Habitable Code over that of Clean Code. In turn Habitability is about discoverability. And discoverability is best facilitated by writing the code as one would describe it using the Naked CRC technique.
This newsletter will be about exploring these ideas of habitability, and in particular about making the coupling between the parts explicit. We’ll see that that isn’t always simple to achieve, but when we do the resulting code is simple and easier to maintain.