The Shape of the System

Make the Bad State Impossible

Most bugs are not things you typed wrong. They are things the code let you type at all.

For years I thought the trick was to get better at being careful. I built up little rituals around the dangerous lines. I'd read them twice before running them, and I kept a list in my head of the things that must never be true at the same moment, and the bugs turned up anyway, on the days the ritual slipped, which was most days. It took me an embarrassing while to work out I'd been losing the wrong argument. The bug was never that I forgot to check. It was that the code let an unchecked thing exist at all. It sat that thing in a variable that should never have been allowed to hold it, and then it just waited. The mistake is usually less something in the code than something the code permitted. You didn't write the wrong thing, exactly. You wrote in a language that let the wrong thing be written down, and then you spent your career apologising for the grammar. More discipline won't fix that. What fixes it is making the bad sentence one you can't say in the first place.

There are two kinds of wrong and we put nearly all our attention on the cheaper one. There's the mistake you make. And underneath it, quieter and much more common, there's the mistake you were allowed to make. Being more careful, adding a check, writing a test - all of that goes at the first kind. The second is the one that keeps coming back, because a check you have to remember is a check you'll forget one day, and the real question was never why did this person slip but why was the slip there to be made at all. Look at the job that way and it changes shape. It stops being about catching bad states as they go past and starts being about setting things up so the bad state has nowhere to sit.

Start with the state itself. Almost every screen you've ever built can be loading, or showing an error, or showing data, or empty, and most of them store that as a little heap of separate flags. One for loading, one for error, one for whether the data turned up. Three booleans give you eight combinations and barely half of them describe a screen that could really exist. The rest are bugs with the safety catch off. They wait for a slow network to find them - the spinner sitting on top of an error message, the empty state painted over stale data, the screen that can't happen and only does when the second request fails before the first one is back. Squash the flags down into a single value that's only allowed to be one of the things it can actually be, and the impossible screen stops being something you guard against. It's just a sentence the code won't let you write.

The same move works on data coming in from outside. There's a difference between checking a value and changing what it is, and that difference is the whole game. Validation asks "is this a valid email address?", nods, and hands you back the same untrusted string, so the next function along has to ask again, and the one after that, and sooner or later one of them forgets. Parsing asks the question once, at the door where the data comes in, and hands back a different thing - not a string that's been checked but an address, a narrow type whose existence is the proof. From there inward the unchecked form isn't dangerous, because it isn't there. Only the parsed thing got through. You spend the carefulness once, at the boundary, and everything inside gets to be ignorant and safe.

Most of this rot grows out of one habit, which is making everything a string or a number. When a user reference, an order reference and a quantity are all just integers, nothing stops you passing them in the wrong order, because to the machine they're the same shape. A bare number is a value that's forgotten what it means. And this isn't a low-stakes problem. In 1999 NASA lost the Mars Climate Orbiter because one piece of ground software gave an impulse figure in one unit and the navigation software read it as another, the two off by a factor of about four and a half. Nobody mistyped anything. A number just crossed a boundary with no unit attached and got read as the wrong thing, and the spacecraft came in at Mars on a path roughly a hundred and seventy kilometres too low, and was never heard from again. The board that looked into it didn't find a careless programmer. It found a verification process that let an unlabelled number cross a seam. Give the number a type and you give it a memory. The wrong combination stops surviving the trip to Mars by refusing to compile here on Earth.

There's one permitted bad state so common it has its own public apology. In 1965 Tony Hoare added the null reference to a language he was designing, because, in his own words, it was so easy to implement. It let any reference secretly be nothing at all, and it let the compiler wave you straight past the place where you forgot it might be. Forty-four years later, on a stage in London, he stood up and called it his billion-dollar mistake, his own loose guess at what all the crashes and holes and forgotten checks had added up to. That figure is a shrug, not an audit. The mechanism is the lesson. A value that's allowed to be nothing, in a language that won't make you admit it, is a bad state the grammar permits, and we've been paying the interest on it for half a century.

The principle goes all the way up into the names too. A reader builds their picture of your system out of the names you picked, and a lying name puts a false picture in their head that no amount of care will fix, because the reader has no reason to doubt it. A function called getUser that quietly creates a user when it can't find one. A flag called isValid that actually means "we haven't checked yet". A list called users that's sometimes not a list at all. Each one stores a wrong idea in a human head the same way a loose flag stores one in a register, except the human can't grep the codebase for it. A truthful name is the same move as an unrepresentable state, done one level up. You're stopping a falsehood from being held - in a mind this time, not a machine.

Put it together and here's the shape of the whole thing. You've got a finite supply of carefulness, and you can spend it at runtime, forever, hoping you stay sharp, or you can spend it once, at design time, building a space where the wrong move can't be made. You won't stay sharp. Nobody does. The win isn't that you turn into a more disciplined person. It's that discipline stops being the thing holding the line. Make the bad state impossible and you're free to be as forgetful and distractible and human as you actually are, because what's standing guard now is the shape of the code, and the shape never gets tired.


In the manifesto, this is tenets (III) and (XXII).

Sources

One of a series of field notes on building software for the way minds actually work: tired, distractible, ordinary, and now partly machine. They all lead back to the manifesto behind them, The Shape of the System.