Main menu:

Site search



Did you write that code three times?

The code isn’t good enough until it’s been written three times.

I have no idea where that adage comes from, but I love it. I could’ve sworn I saw it in the preface to How to Design Programs, but I don’t see it there now. Wherever it was, it rang true to me, so I want to encourage everyone to write their code three times.

There is of course, nothing magical about the number three [1]: I can write hello world well enough in one try, and some particularly complex system might benefit from six rewritings or more.

But for regular challenging work, three often seems to be about right. For nontrivial work, clean design is essential for maintainability, correctness, security, and performance. And creating a clean design ahead of time is generally impossible. There are too many problems to solve at once and too many unseen obstacles. Instead, programmers need to create a rough design, then write some rough code, then see a better design, then revise to some better code, until it’s good enough.

If you take this idea seriously, it may change the way you work.

You might become more patient, expecting your first try not to be good enough. Maybe you intentionally create your first try as a rough draft that doesn’t implement everything and won’t really work, just to see how the design comes out.

If you’re learning to program, or learning new languages or domains, you might take extra care to write everything at least three times at first, so that you can learn more quickly what good design is and how to find it.

If you’re sending your code for peer review, you might not expect it to pass until you’ve written it three times. It might make sense to put it up after the second time, so you can incorporate peer feedback into the hopefully good enough third iteration.

In production work, sometimes there isn’t time to write the code three times. So you can write things only one or two times, but there is a price. For J├ĄgerMonkey, David Anderson wrote the base JIT twice: first the “JM1” compiler that used pre-fatvals 32-bit jsvals and got declared a prototype, then the “moo” compiler that became the final version. The “moo” design was noticeably better than “JM1”, and was certainly good enough to ship in a high-quality product, but it did have its flaws. In particular, the requirements for register allocation were not really understood until the very end of the project, and got put together in a way that was workable, but bug-prone and difficult for programmers to understand and use correctly.

A serious risk to watch out for is shipping research projects or prototypes. Sometimes a person builds something very cool, which they get excited about and want to ship right away. Multiple times I have seen shipping the first iteration of something end badly, or even go badly and then linger painfully. I guess it’s the best thing to do in some situations, like getting some system up quickly for a startup, but it often seems to go poorly with established products. [2] In any case, you should definitely know that you are shipping not-good-enough code and plan for the results.

The difficult thing about all of this is that the problems with code that hasn’t been written enough times tend to come out much later on. Here and now, the code seems to work. Only much later, possibly over the next five years, come the problems and the pain. And there’s no way to really see how all this works without learning that the hard way over the course of a few years.

Including a few minor revisions, I wrote this article once. ­čÖé

[1] Of course, the number three is magical in its deep embedding in human cultures and minds.

[2] Time horizon is important: if the code is to be used for a day, of course it can be low quality; if you’re going to support it for ten years, it’ll be a long ten years unless it’s very high quality. Complexity and debuggability, too: throwing together a new GC in one try is not recommended.