ed. This is a techgaze article. These are published near-real time, as timely discussion on them may be valuable.
So today sucked. And so did yesterday, and the day before. And the day before that we found out the wife has the breast cancer gene. So when it came to quitting time, I went to the local arcade and played a *lot* of pinball. Turns out I ended up thinking about work anyway.
In Austin, everything’s better, so the arcade here is like nothing I’ve ever been to. There are so many pinball machines that I propose an extension of the famous rule #34- Rule #35: if it exists, there is pinball of it. Tonight I saw Roller Coaster Tycoon Pinball, Dr. Who Pinball, Playboy Pinball, and even Space Jam Pinball. Anyhow I digress. This post is about writing software.
I played a lot of pinball tonight, and literally none of the machines broke. Not once. At one point I had five balls in play on Star Wars Pinball, and not one of those balls got stuck anywhere, not once did the score stop counting, not once did the whole thing shut down and throw an error message. That’s incredible.
You may think it’s weird to compare pinball to software. I don’t.
Pinball and software have a lot in common.
If a software developer is going to build a Bumper, the approach will be to define the potential inputs and outputs. The natural thing to do would be to say that the ball hitting the bumper is the only input, and the ball moving away from the bumper is the only output. There we go, two objects (ball and bumper), they both have states, and at least the bumper has methods (which change the state, direction and speed, of ball).
There you go, pinball is just like software.
A ‘hot’ approach to development is test driven development, develop a test for the functionality, develop the functionality, when it passes the test it’s done. Sounds great. All you have to do is define the test cases and you’re golden. More traditional development works the same way, define the stories, build software that fits the stories, done.
Except if you build a pinball machine this way, it would never work.
In pinball, it is simply impossible to define all the stories or all the tests. If you built a bumper using software requirements and design techniques, you may end up with a bumper that works just fine with a ball hits it square on from above, but fails when the ball hits it square from above with a little spin on it. Or worse yet, your built-like-software bumper punches the ball back north, and silently removes the spin from the ball- because you never had a story or test that covered that case.
There are literally infinite situations your built-like-software bumper may be faced with when in a pinball machine. It is literally impossible to explicitly define all of the potential stories and no way to fully test a bumper’s functionality. Test cases and stories will be missed.
It may be possible to define stories on a broad level, but these are very difficult to accurately code to. Stories need to be specific to be coded well- and when you have broad stories, you run into a problem of test coverage. If the stories couldn’t cover every case, there’s certainly no way your tests will cover every case.
Things get even worse when you zoom out a little. Try building an entire pinball machine using software techniques, where you have multiple bumpers, targets, ramps, traps, plungers, lights, sirens, and flippers (which are controlled by an outside system!). The floor may not be level. The player may nudge the machine. Defining all the paths that the ball could take through the machine considering all these factors and more will never happen. The up-front task of defining what needs to happen, when, and how, is a simulation which requires significant supercomputing power, and likely will never be exhaustive.
The Pinball machine built as if it were software would suffer all kinds of issues, most of which would be blamed on incomplete requirements. The table for the ball to roll on would only be built where requirements determined a ball would roll, and as such the table would be riddled with holes. The ball would fall on the floor, and the game would be over.
Additionally, due to the massive list of requirements, the built-as-if-it-were-software pinball machine would have an equally massive amount of code (or, I suppose in this allegory, manufacturing instructions). Considering one bug per 100 lines, I think it’s obvious that between the requirements holes and the bugs, we’d have to name the machine Windows ME Pinball.
Windows ME Pinball sucks. It’s not due to the craftsmanship, or even a lack of effort on the requirements gathering, that team worked *really* hard. It sucks because the approach was wrong. Defining all interactions and outcomes up front and building a blueprint from that is simply the wrong approach to building a pinball machine. I think it could be argued that, at least in some cases, it’s the wrong approach for building software, too.
I think the traditional approach has treated software development well. However, there are two things that when introduced should instantly change your approach.
Random numbers and unknowable properties or methods.
When these things get introduced, you’re playing a new game. It’s time to start coding like you’re building a pinball machine. The pinball machine itself can’t measure the spin on the ball, the machine itself can’t know when the player is going to trigger a flipper, and it can’t know when the player is going to nudge the table.
Thanks to all this and more, it’s severely difficult to predict where the ball is going to go next- yet we often try to do just that- and end up buried in requirements. There may be mountains of requirements, but ironically, they will always be incomplete.
The requirement when building these systems is not to define what is required to happen after an event, but to define what is required to prevent failure.
This doesn’t sound like much, but it really turns the everything on it’s head. Failure is typically the software design catch-all, if observed behavior doesn’t match a requirement, throw an exception and halt execution. In our pinball machine, we have no idea what behavior we may observe. Seeing a new behavior doesn’t immediately mean something has gone wrong. Therefore, we have to design to allow that new, unexpected behavior to continue.
And if that new, unexpected behavior is something going wrong, we need to recover from it gracefully and allow the game to continue, not throw an exception and halt. This is why pinball machines have a piece of glass over the table- the ball shouldn’t leave the table, but if it does, it will hit the glass and fall back in play- not hit the limits of the variable types and become an overflow.
Most people would call the above two paragraphs “robust coding-” an attribute of any good programmer. I disagree. A good programmer codes exactly what the requirements and design ask for, the programmers that change designs are the ones that introduce problems. I call the above failure-preventive design, which should be an attribute of a good architect, especially if they are designing a system with unknowable properties or methods.
The thing is, pretty much every useful piece of software out there today faces unknowable properties or methods. If a system interacts with another system, there’s no way to know what that other system will send to your system. Those properties are unknowable, and as such you better understand what it takes to keep your system from failing, and code such that it doesn’t.
That ball may have a spin on it, and your bumper may have no idea what spin is- but it better be able to bounce that ball anyway, without ending the game.