Authors: Tim Ottinger and Jeff Langr
Font: Burst My Bubble
Programming pundits often decry the dismal state of code in the world. We hear speakers demand professionalism or a more craftsmanlike value system, rigorous certification, etc. In response to these very demands we find contradiction of these very concepts. The argument is frequently made that whether code is "good" or "bad" is subjective and situational. We beg to differ.
To promote a shared set of programming values, we propose these seven virtues of code:
- Working, as opposed to incomplete
A program that works is simply superior to one that doesn't work. We contend that a working program now is of higher value than one that might work some day. To this end, incremental and iterative methods (such as agile methods) push us to complete small features as soon as possible, with improvements and expansions to follow.
We ensure code is working by writing tests before and after writing code as we consider more success and failure modes. We can tell code is working by running the tests and by using the software.
- Unique, as opposed to duplicated
The worst thing we can do to working code is to duplicate it. Copies and near-copies scattered willy-nilly across the code base makes code difficult to maintain. We struggle to eliminate duplication each time we refactor in our red, green, refactor cycle.
A dirty software industry secret: Many "stepback" or "regression" errors are not really re-broken code, but are instead examples of fixes to duplicated code.
We can tell that code is duplicated visually (common paragraph structures) or by the use of duplicate detecting tools like Simian.
- Simple, as opposed to complex
Simplicity here refers to the number of entities, operations, and relationships present in any particular routine/function, and not to the readability of that module (which we call "clarity").
The best way to increase simplicity is to use simpler structures and algorithms. Reducing complexity in this way often translates to improved runtimes, smaller code size, and easier optimization.
We can also improve simplicity of one routine by extracting methods so that a series of manipulations becomes a single step as far as all of its callers are concerned. By moving the extracted methods to the appropriate classes, we also further develop the type system. After extraction, the code still takes all of the same steps, but those steps are evident in far fewer places in the code. The extracted methods are also simpler because they are unencumbered by their original context, a fact which aids us in finding yet simpler algorithms and structures.
Such simplifying code migration is at the heart of object-oriented design.
- Clear, as opposed to puzzling
The meaning and intent of code must be obvious to the reader. Code misunderstandings generate errors. Confusion over code creates delays.
While high-level languages make it easy to see what code is doing, there is still an art to producing code which communicates its goal and intent. The consensus of multiple readers is nonetheless a reasonably consistent measure of clarity. Therefore, the most reliable way to make code clear is to have multiple colleagues reading it.
When one sees an improvement in readability from merely renaming variables, classes, or functions it is because one has improved clarity without changing any of the other virtues of the code. Clarity is further amplified by other virtues such as simplicity and brevity.
- Easy, as opposed to difficult
Adding and modifying code should not be an arduous process. Ease is largely a matter of how much code must be typed in how many places, and how much configuration must change. In a particularly ugly code base, the easiest way to get code working is to implement a hack in an inappropriate place. In a truly clean and simple code base, putting a correct design into place is often as easy as a hack. Uncle Bob Martin has stated that design has degraded when the doing "the right thing" is significantly harder than making "an ugly hack."
- Developed, as opposed to primitive
A primitive system is not necessarily simpler (fewer parts), nor easier (less thinking and typing), nor more clear than a developed system. Primitive code tends to be characterized by Duplication, Feature Envy, and Primitive Obsession code smells. These make a primitive solution more complex, more difficult, and less clear than one built with a well-developed type system.
In an object-oriented system, the developed type system of an application provides well-thought-out classes whose methods make continued development easy.
A system is well-developed when functionality appears to be just where one might expect it. String methods on strings, account methods on accounts, and button activations and the like merely make calls on "business objects."
- Brief, as opposed to chatty
It is valuable for code to be as brief as possible without sacrificing other virtues. This is part of the reason that language tools like LINQ and list comprehensions and closures have become so popular of late. All programmers, including the one who writes it, benefit from writing and reading less code (as long as this smaller amount of code is otherwise clean).
Code that is long and chatty is much more likely to contain hidden errors. An overly cryptic method is likely to be misunderstood. Either one is hard to take in at a glance and understand.
Playing "programming golf" is actually a meaningful activity. If one can make the solution to a problem smaller without sacrificing clarity (or indeed may improve clarity by reducing the solution to a smaller form), then one is reaching a more brief form. The distance from an ideally brief, clear form is unwanted verbosity (chattiness).