Does clean code read like English?

David Rawson
4 min readSep 30, 2023

--

If you’ve dipped your toes into Clean Code detractor literature, you may be familiar with Clean Code’s famous refactor from dirty to clean. We’ll show the dirty code first — don’t spend too long reading it because we’re concentrating on a single aspect of the refactor:

The proposed refactor looks like this:

The refactored code is held up as an example of cleanliness from applying the advice in the “Functions” chapter of Clean Code. Previous criticisms of this chapter have focused on the claim “functions should be small”, but I think it’s worth going beyond the explicit advice in the chapter to find a pernicious implicit.

Lines 2–6 above (includeSetupPages, includePageContent(), includeTeardownPages(), updatePageContent()), held up as “clean”, almost go so far as to suggest that a clean functional decomposition should resemble natural language. In this interpretation, the original version of the code before the refactor was “dirty” because of the unique constructs of programming languages, the if statements, the curly braces and so on. If we can somehow make these go away, or at least hide them, then that will cause the meaning of the code to become transparent. This was, in fact, my own naive interpretation when I first read Clean Code many years ago now.

An English textbook on a bench
Photo by Ivan Shilov on Unsplash

I always suspected others had fallen into this over-correction trap, but I never had evidence. Until recently where I stumbled across the following assertion on LinkedIn:

A Clean Code advice post on LinkedIn

Now that we think about it, one can also find traces of this idea in famous assertions like “switch/case is a code smell” and in libraries that try to replace if/else with extension functions. Or even in the odd article that propounds this tenet:

“Clean code reads like English” is a very dangerous idea to teach to new software engineers.

Yes, too many special cases is a sign of code that is overly complex. Too many if/else can sometimes be a sign of this. Take the example below, a naive solution to the Majority Element problem from LeetCode:

Notice how there are two paths where count is incremented. The inner for block can be simplified to:

achieving solution with only one path.

What can’t, however, be achieved, is removal or hiding of all of the control flow. I attempted a Clean Code-style refactor of Majority Element and ended up with this monstrosity:

I guess I can pat myself on the back from moving all those pesky ifstatements to the bottom of the file and peppering the Kotlin with some English (“updateCandidate”) . But we’ve arrived at solution that isn’t any clearer and doesn’t look like code from a high quality open source library, the sort of library that we aesthetically experience as clean.

Yes, occasionally you will find a way of structuring your functions that makes them read like natural language. The best examples of this might be in lambda blocks that read like English. Consider the following private function in Detekt:

It allows us to write declarations.filter { it.isUsed() } (English!) in our class. However, these occasions will be rare and they aren’t a goal in of themselves. That decomposition works because KtNamedDeclaration.isUsed() is deep and benefits from hiding (abstracting) when we are merely thinking about processing the declarations. It is indeed possible that I may not wish to know how we calculate a declaration is used. This is not at all like updateCandidateForZeroCount in the monstrous refactor where I immediately want to navigate to the definition to get a clearer idea (code!) of what it actually does.

Programming languages lie at some point between the imprecision of conversation and the formalities of mathematics, and if we want to be good programmers, we will have to depart from English and dip our toes into the world of mathematics, even if this is uncomfortable for us.

Clean Code distracts beginners from this task, falsely promising a world where we can make our files read like a Dan Brown novel. Even worse, as we will see, Clean Code focuses on the mere cosmetics of software design. Small functions, use of if/else, number of parameters, avoiding “manager” in a class name and so on are nowhere near as important as the meaningful principles from A Philosophy of Software Design, “modules should be deep”, “design it twice” and so-on.

The cover of The Da Vinci Code by Dan Brown
The Da Vinci Code

Unfortunately, I see this frequently in code review of work from graduates of Clean Code (and, regrettably, in my own code earlier in my career). Some sub-optimal design is plastered over with a few “extract method” refactors. These are often problems that would benefit from careful exploration of design space before committing to a solution. Too bad we’re too busy inserting clumsy prose into our Kotlin to notice.

--

--

David Rawson
David Rawson

Written by David Rawson

Doing Android things in New Zealand. Views here do not reflect the views of my employer.

No responses yet