The Shape of the System

Your Code Has Secret Inputs

Every program quietly reaches outside itself for things nobody wrote down, and those things move the moment you stop watching.

You write a function. A number goes in, a number comes back, and the whole thing fits on one screen so you can read it top to bottom in a single sitting. Nothing goes in that you didn't pass it. Nothing comes back that you didn't return. That's the first thing anyone tells you about code, that a function is a sealed room and the answer leaving by the door depends only on what you carried in through it. It's a nice idea. And in pretty much every program that has ever actually run, it isn't true.

You find out it isn't true on the day the same function, fed the same inputs, hands you two answers that don't match. Or the day it passes on your laptop a thousand times running and then fails the one time, on somebody else's machine, at twenty-nine minutes past midnight. The room was never sealed at all. Someone went round the back and drilled a hundred little holes in it, and the code reaches out through every one of them and grabs something you never gave it. What the time is. Where on the planet it reckons it's sitting. Which alphabet it's decided you're typing. What a coin came up as when it flipped it in the dark. So the real signature of a function isn't the one written in the source. It's that one plus all the things it touches that you never passed in, and that second half, the half nobody wrote down, is where the failures that wake you on a Tuesday night actually live.

Take the loudest one first, the clock. Time is the input nearly every program goes reaching for, and the calendar it reaches into is genuinely broken. Not because anyone was careless. Human timekeeping is just a pile of exceptions with a number painted on the front. Most days you get away with pretending it's simple. A renewal date is today plus a year. An expiry is next year's copy of right now. That holds up for two years and you forget you ever lost sleep over it. Then somebody signs up on the twenty-ninth of February, your code adds one to the year, and it goes and asks the calendar for the twenty-ninth of February in a year that hasn't got one. The date isn't wrong, exactly. It just doesn't exist, and the charge that was hanging off it never fires.

Back in 2012 that very move took out a big chunk of Microsoft's cloud. On the twenty-ninth of February, some of Azure's plumbing went to build a security certificate and worked out the expiry by taking the year it was in and adding one, which gave it the twenty-ninth of February 2013, a day that has never sat on any calendar anywhere. So the certificate couldn't be made, and the component that wanted it gave up, and then a health check timed out and decided the machine must be sick and restarted it, and the machine came back up and fell over in the exact same spot and got restarted again. One impossible date turned into a loop, and it took the best part of a day to chase that loop out of every cluster. Nobody had been sloppy. The code had asked the world what day it was and the world had told it, and the answer it got back was poison.

The same shape of thing froze every thirty-gigabyte Zune on the last night of 2008 - a leap-year loop with no exit that fixed itself the second the clock ticked over into January. And the biggest one is still out in front of us. Loads of older software counts time as a signed thirty-two-bit number of seconds, and at fourteen minutes past three in the morning, UTC, on the nineteenth of January 2038, that number runs clean out of room and flips negative, and the systems holding it will think the year is 1901. We've known that date for decades. It's already on the calendar, sitting there waiting, an input we've been handed years in advance and have mostly just looked away from.

Time is the loudest but it's a long way from being the only one. The machine has a location it never told you about. A timestamp isn't a moment until you also know the timezone, and the timezone is something the code just reads off whatever box it happens to be running on. The same instant shows up as two different readings on the wall in two different places, and twice a year, when the clocks go, one local hour comes round twice while another one never happens at all. Sort two photos by the time the phones stamped on them and you can lock them into the wrong order for good, because one of those phones is still set to a beach its owner flew home from back in March.

The machine has an alphabet as well, and it quietly takes it for granted that the alphabet is yours. Ask most languages to lowercase a word so you can line it up against another, and somewhere underneath a setting is deciding what lowercase is even supposed to mean. In Turkish the letter i doesn't uppercase to the I you're expecting, because the language keeps the dotted and the dotless forms separate, so a login that folds the case before it does the check can quietly stop recognising a name that's spelled exactly right, for every single user in Istanbul, while sailing past every test you ever wrote in English. Turns out "make this lowercase" had an argument in it all along, one you never supplied.

And the machine flips coins. The minute a program uses randomness without pinning the seed, which is just the number it starts counting from, it turns into a slightly different program every time you run it. That's the dull little reason your colleague can't reproduce what you got. Same code, same data, different numbers, all because the one number that actually mattered was the one nobody bothered to write down.

Line all of this up and the most worn-out sentence in software stops being an excuse and turns into a fairly precise diagnosis. "It works on my machine" is true. It works given the dozen inputs your machine quietly hands it and nobody declared - its timezone, its locale, an environment variable somebody set three jobs ago, the exact build of some library the next machine along hasn't got. Send that same code over to a machine that hands it different ones and it is, in every way that counts, a different program. The test that passes for you a thousand times and then fails once in the release build is up to the same trick. It's reading a clock, or a locale, or some scrap of state an earlier run left lying around, and flipping a coin you'd forgotten was in its hand. No amount of being careful patches a hole in the box. The only thing that works is to stop the box reaching through it in the first place.

The cure isn't to be more careful, because careful is the first thing you run out of. The cure is to change the shape of the call so all those secret inputs become declared ones. Stop letting the function read the clock and hand it the clock yourself, and now you can give it the twenty-ninth of February deliberately and watch it fall over in a test on a quiet afternoon, instead of in production on a leap year. Hand it the timezone instead of letting it go off hunting for one. Hand it the locale. Hand it the seed. Pin the versions down so that "the library" means one exact thing and not a moving target. Freeze the environment so a run carries its whole world along with it. And there's one test for whether you've actually done the work. Can you run the thing twice and get the same answer all the way down to the last digit, and can a stranger run it on a machine you've never laid eyes on and get the same answer you did. A program with no secret inputs is a program you can run again. Everything in this piece - the dead cloud, the frozen players, the user locked out of his own account, the result nobody can reproduce - is the bill that came due for code that couldn't.


In the manifesto, this is tenets (II), (XXIII) and (XXIV).

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.