RRF and YAGNI in Practice: A Lesson with Kent Beck
Kent has a framework for approaching software development. He calls it RRF, which stands for 1) make it run, 2) make it right, and then 3) make it fast. Kelly Sutton, another engineer at Gusto, does an excellent job fleshing out RRF in his post on dramatically reducing build times for CI (although he calls it WRF).
“Make it work” means shipping something that doesn’t break. The code might be ugly and difficult to understand, but we’re delivering value to the customer and we have tests that give us confidence. Without tests, it’s hard to answer “Does this work?”
“Make it right” means that the code is maintainable and easy to change. Humans can read it, not just computers. New engineers can easily add functionality to the code. When there’s a defect, it is easy to isolate and correct.
Finally, we “make it fast.” We make the code performant. 1
This framework rests on humility. How? Because we want to believe we’re smart and we are, but we’re not smart enough to solve multiple hard problems at once. When we try to, we tend to come up with inferior solutions 2 — or in my case, generate unnecessary stress.
I had just joined a new team that was working on launching a new product. There was a lot of ground to cover, and each engineer was working on independent projects. Being new, I wanted to make an impression on my team and manager.
My first project was to create a system that shared log files with a third party partner so their systems could ingest the data. The team lead had come up with a technical spec, and it was my responsibility to come up with a design and implement it. The system design I proposed was accepted, and so I got to work.
It was easy enough to create the ActiveRecord models and migrations. But when it came to implementing the classes and jobs for generating the log files, I was stuck, just sitting at my desk looking at an empty file in RubyMine. At a high level, I knew how it should work as well as the inputs and outputs. But I was frozen, trying to think through how actually to implement it most optimally. I’d start out writing a few lines, ask myself if it was optimal, then delete it and start over.
And so I kept thinking. I kept trying to find a way to implement this: so that it would be optimized for performance from the get-go, that the classes and APIs made sense, and that it ran. But I was blocked.
I met with Kent for a coaching session. I told him about the project and what I was trying to do and where I was stuck — wanting to implement a system that worked, and that was also performant.
And he said “YAGNI” 3
“What?”
“You’re not gonna need it — the performant stuff.”
“But Kent…”
“When you need it, you can come back to it. The requirements will have changed. So right now, you aren’t gonna need it.”
So first, I set out to get it running and only focusing on that. It was challenging in the beginning, because it felt counter-intuitive to work in this way. But then I got it running. After that, I tidied it up and refactored it to look clean. Then I was done, even though at the time, it still felt incomplete.
I always have trouble remembering what the letters mean in RRF/WRF. So I came up with my own mnemonic: the 3 Cs. It embodies the same things in Kent and Kelly’s RRF/WRF, but in my own words. So the 3 Cs are:
- Make it Correct: it runs, takes in the right inputs, and gives the correct outputs.
- Make it Clean: easy to read and easy to extend code.
- Make it Quick-er.
In hindsight, it’s funny that I stressed over this as much as I did, because today, most of the “non-performant” code is still there. It’s clean code, and it works. I think it was a combination of ego and imposter syndrome that made me want to impress my team and try and do all 3 Cs in a single (pull request|change list|diff). But I now realize that was the wrong approach.
In practice, I’m usually able to get the first 2 Cs in a pull request. I’m able to get more done when I focus on fewer things. It sounds like common sense when writing it out, but it’s hard to practice in the moment. Occasionally, I’ll see optimizations and get those in. But I don’t force it. “Making it Quick-er” without the need is premature optimization 4, and there are usually other higher priority things to do.
Thanks to Reece Boyd, Guilherme Albertini, Kent Beck, and Amy Shu for reading initial drafts and providing feedback.
https://kellysutton.com/2020/05/18/speeding-up-a-rails-continuous-integration-pipeline.html ↩︎
It’s completely possible there are talented people that can do RRF in one fell swoop and not generate inferior solutions. However, I’d wager the average engineer cannot. ↩︎
Martin Fowler has a great piece on YAGNI: https://www.martinfowler.com/bliki/Yagni.html ↩︎