Code Quality Essentials
Code quality an essential factor for the long-term success of a software system. At the same time, code quality is in most cases opinion-based and up for discussion. Actually testing for bad code is like testing for obscenity: you know it if you see it. After reading Clean Code I realised that this topic is and always will stay subjective. In the end, Clean Code is basically the (expert) opinion of Uncle Bob Martin from 2008.
Recently people (see e.g. here and here) start rightfully questioning the advice in the book. I could start ranting about Clean Code, too, but this would be too easy. A rant without alternative is just hot air: criticism without being constructive, you cannot learn anything from it.
What are the guidelines we really need? I’ll attempt to build my personal, highly
subjective collection, with references to the sources.
» When to test?
My view coincides with Andrew Dalke’s Problems with TDD:
Just use common sense and » good unit testing «, without the extra bells and whistles of TDD.
When to write tests?
- Automated tests after (or during) code development, never before.
- Unit tests should test the API of a cohesive piece of software, something with a clear boundary (aka the unit).
- Use tests to hunt down edge cases
- Adding a new class is not the trigger for a test, but the trigger is implementing a requirement (source)
One test per (desired) external behavior of the unit, plus one test for every bug.
» Design it simple, not easy
Shortcuts = easy, simple = effort to make it consistent and simple to use. Especially in the initial design phase of a feature or system, this approach decreases the headache in the later stages considerably.
» Rough design up-front
I attack architecture by making a rough design up-front. Invest some (perhaps considerable) thinking time into the architectural design. Designs can be iterated fast, written code not. But don’t go too much into detail, just sketch it out. If you add to many details, your design gets outdated fast and confounded by (needless) details.
» One level of abstraction
You should not mix different levels of abstraction in one function. Either the function performs low-level or high-level tasks, but not both. Good to see if you zoom out and look at the syntax colour distribution (cq, nocq).
» No side effects
- functions should do approximately one thing
- that output arguments are to be avoided in favour of return values.
- if you want, you can categorise functions into commands, which do something, or queries, which answer something, but not both.
» Think about namespaces, module & package structure
Module/package structure benefit from being thought out instead of creating a new package/module ad-hoc.
» Delay abstractions and decisions
Try to postpone abstractions and (architectural) decisions as long as possible. If possible. The delay gives you the opportunity to gain more insight with can (and will) influence your decisions and abstractions..
» Impure Functional Programming works best
Some concepts are difficult to realise in one paradigm, so don’t try to force it. Just switch paradigm temporarily. Examples
- functional programming can benefit from imperative constructs
- Functional core, imperative shell (2012) Hacker News
» Function length
Functions should fit on a screen, which translates to about 80 lines. Or 100 lines or something like that.
keep dependencies small, this will keep you code-base maintainable and resilient.
» Develop the critical path first (or early on)
Start with the difficult stuff, but strip it down to the essential functionality. When you get the difficult (aka critical path) running, you have a proof of concept and details or features can be added easily. The critical path needs to be a fundamental goal of a project, it does not make sense to spend time on difficult non-fundamental goals.
Combine with: Rough design up-front and make it simple, not easy.