During several years of writing code for a rather large company (several hundred developers, most of which I have never seen) I came to develop a set of principles for what constitutes quality code.
I am using the word “Quality” in a particular sense here. Code has good quality if it is (in that order)
- less likely to acquire new bugs as it gets modified and extended by people who may never even have met the original developer
- less likely to have unexpected behaviour
- is easy to read and understand.
- doesn’t require a lot of typing
That’s right, I see robustness against future bugs as more crucial that bugs present in the initial version: a piece of code will typically be written once, and modified many times, by many people.
These requirements are really all linked. What protects future developers from mistakes will typically protect the original developer as well. Requiring developers to type lots of code makes it more likely that errors go unnoticed. And it’s harder to see bugs in code that is hard to parse or understand.
I realised protection against unexpected behaviour of a software design against a particular kind of errors can be classified in seven levels, ordered from highest to lowest:
- The code will fail to compile if there’s a mistake. (The error message and neighbouring code contain enough information to find the mistake)
- The compiler will produce a warning in case of mistake. (The warning message and neighbouring code contain enough information to find the mistake)
- Any mistake is visually obvious by looking at a single code section in a single file, and triggers an error message at run-time when the corresponding feature is used.
- A mistake will cause a unit test to fail. (The failure message contains enough information to find the mistake)
- A mistake will cause an error message when the program is started (independently of program input). (The error message contains enough information to find the mistake).
- A mistake will cause an error message when the associated feature is used (independently of how exactly it is used). (The error message contains enough information to find the mistake).
- None of the above.
Level two is assuming that the code isn’t riddled with compiler warnings!
Now suppose I am a maintainer and go modify a piece of code in response to a change request from the customer. If I make a mistake and the code is level one, what I wrote won’t even compile, so if it compiles I can feel safe about my change. If the code is level two I need to check if there are warnings. No compiler warnings? I’m safe. If it is level three I need read carefully the section I modified, but do not need to worry about interactions with other parts of the code. If the code is level four, I need to check the unit tests. Not just run them but also check that what they are guaranteeing is still up to date and covers the potentially new use cases. For a level five code, just compile then start the application. If there’s no message I’m safe. (Same warning as with unit tests – I may have to check the startup verification is still up to date!). For a level six, I need to compile and start the application, then check the feature(s) I modified. If I have a level seven, I need to read all code that may be remotely affected by my change, test everything that may be affected in some way, then pray the customer and end users won’t find more bugs.
My colleagues joked that my quest for high quality code is attempting to do “zen code” because good quality tends to have a form of inherent harmony. This is where the title of this web site comes from. I’m putting quotes because it probably isn’t quite what “zen” actually means.
I’ll be posting examples of typical or less typical requirements, and how various implementations reach different levels. As my day-work is mostly in Java, for now at least examples will be rather java-centric. Apologies for the .net, C, scala and other programmers out there.
Find my first example here and follow from there.