Recently Dan North asked the origin of the software design aphorism “make it work, make it right, make it fast”. Before delving into that story, it’s important to note that I had already heard this phrase. I don’t know where, it’s one of those things that’s absorbed into the psyche of some software engineers like “goto
considered harmful”, “adding people to a late project makes it later” or “premature optimisation is the root of all evil”.
My understanding of the quote was something akin to Greg Detre’s description: we want to build software to do the right thing, then make sure it does it correctly, then optimise.
Make it work. First of all, get it to compile, get it to run, make sure it spits out roughly the right kind of output.
Make it right. It’s time to test it, and make sure it behaves 100% correctly.
Make it fast. Now you can worry about speeding it up (if you need to). […]
Greg Detre, “Make it work, make it right, make it fast”
When you write it down like this, everyone agrees it’s obvious.
That isn’t what everybody thinks though, as Greg points out. For example, Henrique Bastos laments that some teams never give themselves the opportunity to “make it fast”. He interprets making it right as being about design, not about correctness.
Just after that, you’d probably discovered what kind of libraries you will use, how the rest of your code base interacts with this new behavior, etc. That’s when you go for refactoring and Make it Right. Now you dry things out and organize your code properly to have a good design and be easily maintainable.
Henrique Bastos, “The make it work, make it right, make it fast misconception”
We already see the problem with these little pithy aphorisms: the truth that they convey is interpreted by the reader. Software engineering is all about turning needs into instructions precise enough that a computer can accurately and reliably perform them, and yet our knowledge is communicated in soundbites that we can’t even agree on at the human level.
It wasn’t hard to find the source of that quote. There was a special issue of Byte magazine on the C programming language in August 1983. In it, Stephen C. Johnson and Brian W. Kernighan describe modelling systems processing tasks in C.
But the strategy is definitely: first make it work, then make it right, and, finally, make it fast.
Johnson and Kernighan, “The C Language and Models for Systems Programming”
This sentence comes at the end of a section on efficiency, which follows a section on “Higher-Level Models” in which the design of programs that use C structures to operate on problem models, rather than bits and words, are described. The efficiency section tells us that higher-level models can make a program less efficient, but that C gives people the tools to get close to the metal to speed up the 5% of the code that’s performance critical. That’s where they lead into this idea that making it fast comes last.
Within context, the “right” that they want us to make appears to be the design/model type of “right”, not the correctness kind of right. This seems to make sense: if the thing is not correct, in what sense are you suggesting that you have already “made it work”?
A second source, contemporary with that Byte article, seems to seal the deal. Butler Lampson’s hints deal with ideas from various different systems, including Unix but also the Xerox PARC systems, Control Data Corporation mainframes, and others. He doesn’t use the phrase we’re looking for but his Figure 1 does have “Does it work?” as a functionality problem, from which follow “Get it right” and “Make it fast” as interface design concerns (with making it fast following on from getting it right). Indeed “Get it right” is a bullet point and cautionary tale at the end of the section on designing simple interfaces and abstractions. Only after that do we get to making it fast, which is contextualised:
Make it fast, rather than general or powerful. If it’s fast, the client can program the function it wants, and another client can program some other function. It is much better to have basic operations executed quickly than more powerful ones that are slower (of course, a fast, powerful operation is best, if you know how to get it). The trouble with slow, powerful operations is that the client who doesn’t want the power pays more for the basic function. Usually it turns out that the powerful operation is not the right one.
Butler W. Lampson, Hints for Computer System Design
So actually it looks like I had the wrong idea all this time: you don’t somehow make working software then correct software then fast software, you make working software and some inputs into that are the abstractions in the interfaces you design and the performance they permit in use. And this isn’t the only aphorism of software engineering that leads us down dark paths. I’ve also already gone into why the “premature optimisation” quote is used in misguided ways, in mature optimisation. Note that the context is that 97% of code doesn’t need optimisation: very similar to the 95% in Johnson and Kernighan!
What about some others? How about the ones that don’t say anything at all? It used to be common in Perl and Cocoa communities to say “simple things simple; complex things possible”. Now the Cocoa folks think that the best way to distinguish value types from identity types is the words struct
and class
(not, say, value and identity) so maybe it’s no longer a goal. Anyway, what’s simple to you may well be complex to me, and what’s complex to you may well be simplifiable but if you stop at making it possible, nobody will get that benefit.
Or the ones where meanings shifted over time? I did a podcast episode on “working software over comprehensive documentation”. It used to be that the comprehensive documentation meant the project collateral: focus on building the thing for your customer, not appeasing the project office with TPS reports. Now it seems to mean any documentation: we don’t need comments, the code works!
The value in aphorisms is similar to the value in a pattern language: you can quickly communicate ideas and intents. The cost of aphorisms is similar to the cost in a pattern language: if people don’t understand the same meaning behind the terms, then the idea spoken is not the same as the idea received. It’s best with a lot of the aphorisms in software that are older than a lot of the people using them to assume that we don’t all have the same interpretation, and to share ideas more precisely.
(I try to do this for software engineers and for programmers who want to become software engineers, and I gather all of that work into my fortnightly newsletter. Please do consider signing up!)
This extends outside of the programming world too: The “one bad apple” phrase is often used to indicate an outlier these days, which should be ignored, while the original quote was “one bad apple spoils the bunch”, indicating the whole system/police department/basket needs to be thrown out and re-assembled from scratch.
Somewhat ironic that in Dijkstra’s original paper “Go To Statement Considered Harmful” he stated: “… the programmer’s activity ends when he has constructed a correct program…”. Still, he was writing in the computing Steam Age when design mostly consisted of ensuring your statements could fit in the number of columns available on the punched cards. :-)
I attended a lecture by Dijkstra in Oxford in the late 90’s: I remember being disappointed by the boring delivery and lack of any insight into contemporary computing. He still had a very 1960s “mainframe” view.
Very interesting article.
I see aphorisms as encoded messages. The information reduction seems natural (as in human behavior) and tends to pursue the remaining of the message’s essence. So to interpret it properly requires effort.
The appeal to “mere correctness” as the end goal of a programmer, seems to be a reminiscence from the days where programmers were mostly mathematicians.
From my experience as a software developers we are always trying to understand the context to find the proper balance between two opposing forces: cost of labor vs computational efficiency (aka scalability)
Correctness is a pre-requisite. If the software doesn’t work, there is no value being created. But if it “merely works” the value created may not surpass the cost over time.
So the drive to make it right is to achieve a good enough level of abstraction and design that:
1. reduces the cost of maintenance to keep it running.
2. reduces the cost of change to keep it adaptable.
3. reduces the cost of growth to keep it stable over time.
The “make it fast” part, despite coming last, seems to have intersections with “make it right”, because at some point the design may increase or decrease the difficulty of delivering the proper computational efficiency.