Take it slow, I'm in a hurry

Software quality isn't a cost you pay on top of delivery. It's how you avoid doing the same work twice.

There's a saying I like a lot. Take it slow, because I'm in a hurry.

It sounds like a contradiction. It's the whole case for software quality.

Quality work is, in a way, work for lazy people. The earlier a problem is found, the easier and faster it is to fix, and most of the time you only have to fix it once. A bug caught while you explain your code to a colleague costs a few minutes. The same bug found by a user costs a report, an investigation, a fix, another review, another release, and some of that user's trust.

Quality isn't a cost on top of delivery. It's how you avoid doing the same work twice.

The slowdown is short

The usual objection is that quality slows you down. It does, at first. Martin Fowler drew the trade-off in a graph I keep coming back to.

Quality slows you down for a few weeks, then speeds you up

Functionality delivered over time, with high and low internal quality. After Martin Fowler's graph in “Is High Quality Software Worth the Cost?”.

functionalitytimeweeks, not monthshigh internal qualitylow internal qualityfaster and cheaper from here on

A team that skips quality delivers faster for a while. Then every change has to work around the shortcuts taken before it. Changes take longer, break things nobody expected to break, and need more testing by hand. The curve flattens.

A team that invests in quality starts a little slower. Then each change builds on clean ground, and the curve keeps climbing. The point where the two cross comes sooner than most people expect. It's measured in weeks, not months.

I've lived on the flat end of that curve. On the apps I maintained before a rewrite, an average change took three to four days, and one took two months. I wrote about it in Before you rewrite it.

Prevent, reduce, contain

Ensuring quality isn't one practice. It's a few layers, and each one catches some of what the one before missed.

Each layer catches what the one before missed

Problems moving toward production. Review stops some, tests stop more, and containment limits the damage of what gets through.

ProblemsReviewTestsMetricsProduction

Code review

Explain your code to a colleague: what it's for, how you did it and how you checked it. Often, explaining it is enough to find the problem yourself.

Pull requests are the usual way to do this. Pair programming does it while the code is being written, which is even earlier. Either way, keep changes small. A reviewer reads a small change carefully and skims a big one.

Tests

Find the causes of defects, and test. Write unit tests for the logic and integration tests for the parts that work together. If you want to commit fully, add end-to-end tests that run the app the way a user would.

I use Jest and React Native Testing Library. Every stack has tools for these levels. Pick the ones your team will actually run, and run them on every change.

Static analysis belongs here too. A linter and a type checker catch a whole class of mistakes before anyone writes a test.

Metrics

Look at the shape of the code: cohesion, coupling and size. You want high cohesion, so each module does one thing, and low coupling, so a change in one module doesn't ripple through the others. Size is the early warning. A file or a function that keeps growing is usually doing too much.

Containment

Some problems will reach production anyway. Containment limits what they can do there.

Fail in a way that leaves the rest of the app working. In React, an error boundary keeps one broken screen from taking the whole app down. Put risky changes behind a flag you can turn off. Roll releases out gradually, so a bad one reaches a few users before it reaches all of them. And find out about errors before your users report them.

Where to start

You don't need every layer on day one.

Start with

  • Code review on every change
  • Small pull requests
  • Unit tests for the logic that matters
  • Integration tests for the main flows
  • A linter and a type checker

Add as you grow

  • End-to-end tests
  • Metrics for cohesion, coupling and size
  • Error boundaries and fallbacks
  • Feature flags and staged rollouts
  • Error monitoring in production

If you're a small team, or working alone, the left column is enough for a long time. The right column matters more as more people change the same code.

What the research says

The research on inspections is old. It still holds up.

What inspections gave back

Reported gains from code inspections. Cost from Kalinowski. Time and productivity from Gilb and Graham, 1993.

0%10%20%30%40%50%Implementation cost39% lowerDevelopment time10 to 30% shorterProductivity30 to 50% higher

Inspections also make costs and schedules more predictable. Fewer surprises late in a project means fewer dates that slip.

Isn't it too slow?

Maybe you're thinking you can't do all of this and still ship on time. Sometimes that's true. A demo next week or a hard deadline can make it reasonable to skip something now.

But you should know what you're trading when you skip it. The time you save comes back later as rework, with interest. Make that trade on purpose, write down what you skipped, and pay it back soon.

Take it slow, so you don't have to do it again.