1. Make it work 2. Make it right 3. Make it fast. So many projects start at 1 and skip to 3, or worse, start at 3… :-(
In reply, Conal Elliott tweeted
What does it mean to work but not be right?
which is a great question. If a program can be said to be “working”, what can that possibly mean except that it is in some sense a “right” program?1
That’s a pretty decent point. Software is a tool for getting stuff done, after all.
However, I’m not quite ready to abandon my idealism about these things, so let me have a stab at expressing myself more clearly: a “right” program follows the design implied by its own implementation. Implementing programs can lead to improved designs through deeper understanding of the underlying domains.
Now, programs can “work” just fine and yet not follow (or not expose) their own internal logic. It’s when programs are composed that their little warts and kinks start to add up quickly into serious distortions. Let’s revisit the adage:
- Step 1: make it work;
- Step 2: make it right;
- Step 3: make it fast.
I have in my mind the image of two mirrors roughly facing each other (step 1): improving their alignment even slightly (step 2) can cause the tail of reflected images to grow asymptotically longer. Polishing the mirrors (step 3) helps sometimes, but only if they’re in good-enough alignment to begin with.
I don’t care about step 2 for every piece of software. For example, most embedded systems software (microwave controllers, digital watches) is so throwaway and terminal that polishing it beyond a certain point isn’t worth it.2
Foundational software, though, is where a “make it right” step becomes crucial. Software upon which other software is constructed3 has to be made right because of the costs of correcting for design flaws downstream. The total effort involved in working around kinks and warts in a foundational artifact often exceeds the effort required to fix the foundations.4
There are countless examples where an almost-right product has gained wide influence without being “made right”: Unix, whose distortions have led to X-windows and monolithic walled-garden applications; Excel, whose distortions particularly with respect to naming and scope have led to horrible kludges and workarounds in any application beyond the trivially simple; AMQP, whose original model was simple and promising, but which is now moving in the direction of the irredeemably complicated; and on, and on.
In each case, lots (and lots) of effort has been spent on optimizing the current artifact, and because very little effort has been spent on learning more about the domain and revisiting the artifact’s design decisions, even more effort has been spent downstream on workarounds for each system’s design flaws.
Lots of these cases have been successful because the software involved has happened to line up well enough with the underlying domain that new kinds of useful work can be done: getting the foundations closer to being “right” has opened up previously-unimagined spaces for new applications. Getting the foundations “right” really does increase the power and the reach of a system.
Er, on second read, perhaps Conal was being ironic, and saying that a non-right program in the sense of this post cannot be said to work at all! After all, without semantics, programs are meaningless… ↩
Although even here there are lessons to be learned. An embedded system’s distortion with respect to its own internal logic, if examined carefully, can lead to less-distorted future iterations within a product line, perhaps, and the lessons may even spill over in the course of an engineer’s career into other embedded systems. ↩
For instance, operating systems, language virtual machines, language designs themselves, important libraries, and networking protocols, etc. ↩
Which makes it especially important to get network protocol designs right, since flaws and felicities are magnified so strongly by the network effects involved. ↩