An exceedingly clean code
Update 9 Feb 2017 : _Today, it is by far my most read article, and also the most hated. I didn’t intend to publish the holy grails of clean code, there is already a whole book for it. As the title implies, the quality is pushed a little too far, and achieve it on every function is probably not so realistic. I tried to explore new ways to make the code more readable, without too much care about performance. You don’t have to agree with / like it :) Take it easy.
The article is written with ES6 for examples, but the article could be applied to other programming languages.
- F*** what does this function try to…
- stop, the dev who made it left last week
- well, since _.zip is used, proposals and checkboxes are probably arrays.
- good … arrays of what? Didn’t know _.zip, I discovered lodash with this project.
- and what’s the point of line 4 condition ?
- I dunno.
- …
A small, very small JS function of 11 lines turns into a maintenance nightmare. The slightest bug, the slightest recovery, the slightest refactoring costs a lot of time and energy.
Knowing that any software is counted easily in thousands of lines of code …
Version 0 : do not change anything
And write automated tests. No need to be unit tests written the TDD way. Integration tests written afterwards are just as good enough to begin with. Or acceptance tests.
Without any test, it’s impossible to improve anything.
Voluntarily I do not show the unit tests of this function, the purpose of the article is to give an example of documentation by the code.
Version 1 : Check preconditions
In order to help the reader, while improving the robustness of the code, and to avoid the edge effects, let’s be sure of the input parameters before going further.
We understand what the input parameters are, but that’s hard. Have you noticed the “!” Exclamation mark ?? No ? So why not write some utility functions that makes it great:
And even better, we use an IDE plugin to get a nice alignment
There you go ! No need to comment at this stage, it’s clear as water.
We use Ruby-like code, where each word is placed in the right order to be sure we will able to read the code as in natural language. For example, here one can read in the 2nd line, excluding everything that is not strictly textual:
if proposals is not array of string return []
There you go ! Again, no need to comment until then.
Note 1 : An effective way of fighting the anxiety of “but-what-if-this-happens” (and against the debugging hours!) is to guard against any unexpected parameters by returning an empty value corresponding to expected type. This is the behavior chosen in a majority of cases for lodash / underscore. (For example, returning an empty string if the function returns a String, an empty array if the array must return an Array, etc.).
Note 2 : Even a strongly-typed language wouldn’t have solved all problems. The checks on the difference of the array sizes / on the non-empty array would still have taken place.
Version 2 : Comment by example
Comment the signature of the function. But-that-serves-nothing-if-the-function-is-well-named.
Not exactly.
- The general idea is: document by example.
- These parameters are relocated in the overall context of the project (for example, it is understood that proposals are proposals or questions posed to the user)
- By the way the type of parameters of the function are now evident.
- Any odd / counterintuitive thing can be reported here. With the help of the word WARNING or XXX in the comments. It’s a bit like Github: do not be afraid to abuse it. There will ALWAYS be issues, always odd thinks in your code, even if you do not like it.
Version 3 : Verticalize your code
This StackOverflow answer is a good example of verticalization.
In our case, the use of applicative programming make code greater.
Even on a very small function, think about what happens when the flow of instruction looks like this
This is particularly painful, isn’t it?
On the other hand, a vertical instruction flow make things much simpler.
Applied to our example, we replace
By this:
We take a breathe! No decision tree, no “unambiguous” variable (item), functions that make a unitary job …
We comments again by example, line by line:
Final Code
Small synthesis
The code must be covered by automated testing. It does not matter whether they are unitary, of integration, of acceptance. The absence of a safety net prevents the code from being improved.
The input parameters must be checked. Although this point is particularly subject to debate in the case of a dynamic language, it will help you to debug your code faster, and also increasing the readability of the code.
The functions must be as small as possible, with a maximum explicit naming.
A readable code is not necessarily the shortest.
Avoid any for loop, application programming simplifies reading.
any (relevant!) comment is good, but the best remains the explanation of the nominal case.
Aligning characters helps to make the code readable.
Any oddity, counter-intuitive or that makes you think for more than 2 seconds must be written in black and white using a statement previously made by the team members (XXX most often).
To avoid overloading the article, naming variables has not been mentioned, whereas it is probably the hardest thing in computing. You’ve been warned :)
It’s never really over :
- a recent article suggests not to use _.chain
- we should at least put a warning if an input parameter is not valid in our example.