The secret of good programming 🕵
I’ve been reading the book On Writing Well by William Zinsser. It contains the following explanation of what “good writing” is.
The secret of good writing is to strip every sentence to its cleanest components. Every word that serves no function, every long word that could be a short word, every adverb that carries the same meaning that’s already in the verb, every passive construction that leaves the reader unsure of who is doing what—these are the thousand and one adulterants that weaken the strength of a sentence. And they usually occur in proportion to education and rank.
Zinsser talkes about writing non-fiction, but I feel it applies to programming as well.
Let’s take a look at how it translates to writing code.
In the context of programming #
The secret of good programming is to strip every statement to its cleanest components.
I chose to replace “sentence” with “statement” here, but it could really have been “function”, “file”, “class”, “module” or any other grouping of code.
As for “cleanest components”, the meaning is the same–to use the tools that solves the problem without introducing unnecessary complexity, and are easy for the reader to understand.
The second sentence allows for many translations, but here’s one one take.
Every operation that serves no function, every long expression that could be a short one, every construct that duplicates existing functionality, every indirection that that leaves the reader unsure of who is doing what—these are the thousand and one adulterants that weaken the strength of a statement.
These are by no means the only things that weaken the strength of your code. In fact, I suspect we could spend all day coming up with more examples.
The last sentence is the real kicker.
And they usually occur in proportion to education and rank.
Just as Zinsser notes, the amount of excess is often proportional to education and rank. It would be convenient to blame bad code on “people who are not as knowledgable as we are”, but would it be true?1
A concrete example #
As an example of the above, the following code blocks all do the same thing.
val listOfElements = document.getElementsByClassName("item")
val elementIdList = mutableListOf<String>()
for (element in listOfElements) {
elementIdList.add(element.id)
}
val stringBuilder = StringBuilder()
fun withCommaUnlessFirst(elementId: String, index: Int): String {
return (if (index > 0) "," else "") + elementId
}
for ((index, elementId) in elementIdList.withIndex()) {
stringBuilder.append(withCommaUnlessFirst(elementId, index))
}
val output = stringBuilder.toString()
return output
The above code is quite verbose. It uses mutable state and explicit iteration. It attempts to encapsulate the comma separator logic into a function, but mostly makes it worse by hiding it from where it is used. It has unnecessarily verbose variable names which duplicate information available in the types. We can do better.
val elements = document.getElementsByClassName("item")
val elementIds = elements.map { it.id }
return elementIds.joinToString(",")
This second function improves the situation a lot. It reduces the code to three clear steps: get elements, extract their ids, and join them to a string. It simplifies variable names. It makes use of existing standard library functionality. It avoids unnecessary indirection.
The purpose of this example is not to show that the second version is shorter, that is just a nice side effect. The point is that it is clearer. It says exactly what it does, and nothing else. And it does so without becoming an obscure oneliner. In the language of Zinsser, the second version is stronger than the first.
Further tweaking #
Depending on taste and knowledge of the Kotlin standard library, one could argue that we could improve it further.
val elements = document.getElementsByClassName("item")
return elements.joinToString(",") { it.id }
This third version makes full use of the joinToString
function from Kotlin’s standard library. It allows us to merge the map
call into the joinToString
call. However, it may become a little bit less obvious to developers not used to Kotlin.
As a final adjustment, we could even remove the elements
variable and chain the two functions into a single expression.
return document
.getElementsByClassName("item")
.joinToString(",") { it.id }
Whether this is better or worse is a judgement call to make from case to case. I think a well-named variable every now and then can make a lot of difference in readability as well as debuggability.
The secret #
To me, the second version goes a long way towards discovering the secret of good programming. Writing code that is clear and to the point, without trying to be too clever.
What about you? Which one would you write? Which one would you want to maintain?
-
It is similar to my remark in Basic tools: The intermediate developer learns to use more advanced tools, and finds many ways to apply them. They may often take pride in using these tools, viewing it as proof of their advancement. ↩︎