The Servers Are Burning
&gt; <i>in order to write effective tests, a programmer had to know all of the ways that a piece of software could fail in order to write tests for those cases</i><p>No.<p>In order to write effective tests, a programmer has to think of the piece of software&#x27;s entire input domain, carve it up into a set of equivalence classes, and then determine what the expected behavior should be for a piece of input from each of those classes. With more careful testing at their boundaries, so that you can root out edge and corner cases.<p>The results of those tests will then find the ways your software can fail for you.<p>Thinking about it this way, it starts to become clear that a huge part of your job is finding ways to simplify that input space as much as possible. Simplifying at this stage makes getting it right so much easier. The fewer special, edge, and corner cases you allow in the first place, the fewer you have to code for, the fewer you have to test for, the fewer you might accidentally miss, and the fewer others might accidentally run afoul of.<p>This, incidentally, is the key reason why it&#x27;s good to take some time to define all your tests before you start writing your implementation. I&#x27;m not necessarily a huge advocate for &quot;red green refactor&quot;, but I do think that at least identifying all your test cases before you ever start implementing can potentially save you boatloads of time, by helping you recognize opportunities to simplify your design <i>before</i> you get locked into an unnecessarily complicated one by a couple hours or days (or months) of sunk cost.<p>It&#x27;s also the real reason (IMO) why functional programming - as a style, not a kind of language - is such a valuable discipline. The challenge with programming in an imperative style is that it turns your module&#x27;s entire past history into one of the inputs you need to consider, and that turns your test and specification surface into something that is just so much bigger. Considering that it opens up the possibility of injecting bugs into a routine without ever actually editing the routine itself, or even any of the functions it calls, it&#x27;s possibly even fair to say that it&#x27;s unbounded.