Making it fast shouldn't be the last step
Monday, September 25, 2023
There's a common quote in the software world that you should "make it work, make it right, then make it fast."1 This is a catchy aphorism, and it is often taken as a rule. But in its short form, it misses some crucial nuance. Let's unpack it to see what's missing, then how to do things right.
What does it mean?
Unpacking the statement, we have three distinct phases.
First, we make it work. In this step, you get something working. It should handle a basic case of the problem you're solving, but doesn't need to handle edge cases. Sometimes you might skip tests, sometimes you might make a mess. But you show that it can be done and figure out roughly what it will take.
Then, we make it right. This step is where you tighten up all the loose ends from the first step. Handle all the edge cases, test the code, clean up any messes, do any refactoring. The end result here is a working artifact that meets all the requirements.
And then, we make it fast. This is the step I see skipped all the time, and it's where you go back and speed things up. You do some profiling, see how things perform, then tweak and improve until you have satisfactory performance.
Here's the problem: You are in for a world of hurt if you leave "make it fast" for the last step. And that's probably why we have so much slow software in the world.
After you've gone through the trouble of making software right, if you have major performance problems, they are likely to be fundamental to the approach you took. You'll be able to speed things up somewhat, but major improvements will require more invasive and painful refactoring. This is often simply not given enough time, and we hack things up to limp by.
The reality is that if you want to have a hope of making your software perform well, you have to think about performance from the beginning. You wouldn't start making the software without thinking about what correctness is. Nor should you start it without thinking about how to make it fast.
When you start making something, to make it work, you have to have a conception of what "make it right" will look like so that you can design with that in mind and not back yourself into a corner. It's exactly the same with "make it fast."
You have to make sure from the outset that your architecture supports the performance you need. Otherwise you may wind up with decisions that are difficult to reverse but stand between you and performance, and then you have to practically rewrite the whole thing. And it's not just the architecture level. Every line of code you write impacts performance. People want to profile a codebase and find the line of code that's making it slow, but usually it's endemic and spread throughout. Small penalties spread throughout the whole project add up to a big total cost.
Instead, this is a crucial part of "make it work." In that first step where you handle the common cases and think about (but don't yet handle) the edge cases, you must do the same for performance. Make sure that "make it work" includes an implicit "fast enough when in realistic conditions." Then as you layer on correctness, you can keep ensuring performance is sufficient.
But the aphorism is rather catchy...
We need a different saying
Unfortunately, it's hard to capture nuance in an aphorism. I think it's important to try, though: This aphorism in particular is one I've heard used to justify some sloppy work which ended up painting projects into performance purgatory2.
So what do we say instead?
I'm not sure. Writing aphorisms isn't my forte! The results from the LLMs I tried were also not great. Suggestions are welcome, but I think the answer might be that we don't need an aphorism here.
Instead, we lean into the fact that we're doing engineering and we have to design all the requirements into the software, including performance. We don't need an aphorism to justify our work. We just have to remember that performance does matter (and is part of being correct for much software) and that it's a consideration throughout the entire process.
This is often attributed to Kent Beck, but was published at least as early as 1983 by one Brian Kernighan.
Pretty pleased with the alliteration.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe to the newsletter or use the RSS feed.
Want to become a better programmer? Join the Recurse Center!