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?

Difficult question. I found some random notes on » The Internet «, Is TDD Dead? and the corresponding HN thread.

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.

See also

» 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.

Conceptual Integrity can help in the design process.

» 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

Minimise functions with side-effects (cq, nocq).

  • 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.

» 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..

» Function length

Functions should fit on a screen, which translates to about 80 lines. Or 100 lines or something like that.

» Coding conventions deterioate over time

If you have coding conventions, make sure they are kept intact.

Pseudoexample:

In front-end development the CSS styling approach BEM is a convention and often not enforced, so over time, as team and devs are changing, it will get watered down.

How to deal with this?

  • Educate: have documentation ready and put effort into onboarding new devs.
  • Enfoce: Use tooling to encode core architecture, style and intent in the linting rules (take care to not overdo it).

» Dependencies

Keep dependencies small, this will keep your 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.

» Consistent naming

Function names should be descriptive, consistent and verb phrases. Choose them carefully (cq, nocq).

» Composition over Inheritance

Composition over inheritance - Wikipedia

» API design

APIs, like diamonds, are forever. So think twice before changing it.

» Think about namespaces, module & package structure

Module/package structure benefits from being thought out instead of creating a new package/module ad-hoc.

» Impure Functional Programming works best

Some concepts are difficult to realise in one paradigm, so don’t try to force it. Just switch the paradigm temporarily. Examples

» Cohesion

Ignore the single resposibility principle, use cohesion instead.