21 Dec 2018 Doing The Simplest Thing That Can Possibly Work (When You’re Not Sure What To Do Next)
From time to time, developers will come to my desk seeking advice. There’s a problem they’re trying to solve, they’ve got a couple of possible solutions, but they’re not sure which one to choose. I have a standard approach for dealing with this.
First, I start by asking them what the problem is. If they can’t articulate that, I usually tell them to go away and only come back when they can. If they can tell me, next I ask them to explain to me the different solutions they are considering. To be honest, at this point I usually only have a vague understanding of what they’re talking about. However, I nod and make encouraging noises.
Next, when they finish explaining the different solutions to me, I’ll say this:
“What’s the simplest thing that can possibly work?”
They’ll think for a bit. There might be a bit of debate between them. Once again, I won’t really understand what they’re talking about.
Eventually, they’ll reach a consensus and tell me which is the simplest option. This is my cue to say:
“Well, that’s what I think you should do”
They’ll nod vigorously. “You’re right Ben, that is definitely what we should do”, they’ll say. They walk away, thanking me for helping them find a direction forward. I return to my work, still not really having much of a clue as to what they were talking about.
The idea of Doing The Simplest Thing That Can Possibly Work (When You’re Not Sure What To Do Next) is the single-most important engineering guideline I have found in my career.
For brevity in this post, I’m going to simply refer to it as “Doing The Simplest Thing That Will Work”, and will drill into why it’s so important. I’ll also get into the second, parenthesised part of this guideline, and argue why, even if you think you know what to do next, maybe that’s not really true – and that’s OK.
Solving real problems
Doing the simplest thing that will work means that you solve the real problem in front of you, not imagined problems. It means that, given the option of choosing to do something now, “just in case”, or deferring that decision, you err towards the latter.
Why do this? Because a codebase represents the culmination of all the decisions that have been made about that project or product so far. Furthermore, the tradeoffs and consequences of each decision compound the complexity of those that have come before. When you make a decision earlier than you need to, you prematurely accelerate the complexity of your system.
The irony is that, because the purpose of our software is to insulate end-users from some inherently complex part of the world, you don’t need to seek-out complexity. It will come and find you eventually anyway. The only question is how weighed-down you will be by additional complexity of your own making.
Doing the simplest thing that will work also means continually picking the line between over-engineering and under-engineering solutions to problems. This is hard, and sometimes involves value judgements. Yet I believe that, after actually delivering software, correctly navigating this course is our most important job as software developers.
Doing the simplest thing that will work means you use the tool that is most appropriate for the job, rather than the tool that happens to be hot right now. It also means you don’t succumb to the streetlight effect of focussing on the problem you know how to solve, rather than problem you need to solve.
It means you avoid wasting time agonising over unnecessary decisions, which in-turn causes you to neglect other, more mundane-but-important areas of your system. It helps avoid systems that are over-engineered in all the wrong places and under-engineered everywhere else.
Finally, doing the simplest thing that will work means recognising that, if some code doesn’t seem worth the effort of testing or documentation, it’s probably not worth having that code at all. Nothing seems to constrain the volume of code we produce more than the prospect of having to rigorously test and document everything we do.
Writing boring code
If all of this sounds kind of boring, you’re both right and wrong.
Doing the simplest thing that will work means that you aspire to write boring code. This is code where people look it and say “Oh yeah, that seems straightforward, I get that”, and move on. You try and contain the areas of unavoidable complexity in small, isolated, well-commented chunks. You adhere to the principle of least astonishment.
And what’s even better than boring code? No code. For many developers, the sweetest feeling of all is deleting code, or managing to avoid having to write it in the first place. The best developers work hard to avoid work.
Don’t get me wrong – you won’t necessarily achieve the goal of perfectly boring code in one hit. It’ll probably take a couple of iterations. For each iteration, I recommend the Think a Bit, Code a Bit, Test a Bit strategy. The key part is that, after getting something working, you follow-up and repeat the loop until you have stripped things back to the bare minimum needed. That way, you won’t be condemning future developers to have to pick apart the necessary, inherent complexity in your code from that which is actually unnecessary.
Doing the simplest thing that will work also means that you’re not afraid to start small, knowing that every complex system that works is invariably found to have evolved from a simple system that worked. It’s also about having faith that you’ll be able to learn and adapt as your understanding of the problem evolves.
Finally, whilst the end result might be boring, the process can actually be quite challenging. Finding the simplest thing that will possibly work can be hard. It might not be the challenge you envisaged for yourself when you got into the business of building software, but it’s still a challenge. Furthermore, it’s a challenge that will lead you to a deeper understanding of the tools that you’re using, and push you to learn better ways of doing things, rather than churning out cookie-cutter code.
This is where we start to question whether, when we say we know what to do next, we really know.
Doing the simplest thing that will work comes with the assumption that we don’t know what to do next. This is hard for some people to admit, as it requires acknowledgment of the inherent chaos and unpredictability of the world. It requires acceptance of the limitations of our ability to reason about and predict the future.
It is also a relinquishment of over-engineering’s illusion of control. Our systems are sufficiently complex that we cannot hold them all in our heads any more. Some people may be able to hold more in their head than others, but everybody has their limit.
Some people hope that, having written the code once, they’ll never have to work with it again. Consequently, they try to get everything right the first time. I gave up on this practice long ago. Very occasionally, I’ll pre-emptively put something in because I know I’ll need it tomorrow, or maybe next week. However, I don’t pretend to have much idea what’s going to happen beyond that.
Doing the simplest thing that can work is also about recognising that today’s greenfield project is tomorrow’s legacy system. It is an acknowledgment that systems naturally decay, software rots, and entropy increases as we move towards the heat-death of the universe. That might sound grim, but accepting it is actually liberating. You are no longer burdened with trying to control the uncontrollable, but must merely navigate it.
So doing the simplest thing that will work means that, in the face of all this uncertainty, you must create an environment that can support a high level of change. For example, for most non-trivial projects, automated tests give your team the confidence to touch things without fear of breaking them. This in-turn enables a culture of continuous refactoring, which leads to avoiding difficult-to-understand code and saves you from an exponential proliferation of bugs. Additionally, for any project bigger than one or two people, a peer-review process gives everybody their best opportunity to have a say regarding whether new code will work and also makes sense.
Finally, doing the simplest thing that will work is about acknowledging that we are all learning here. In making decisions, we sometimes make mistakes. In making mistakes, we learn. A codebase is a representation of what your team has learnt about your project or product during its lifetime. The learning and the building are tied together.
But sometimes, people over-engineer to avoid the risk of making a mistake. They prefer to put something in now “just in-case”, rather than risk looking bad if they leave it out and it turns out to be necessary later.
This results in a codebase muddied by code whose validity has not been confirmed by real-world experience. It will confuse other developers on your team, both now and in future. Time spent understanding unnecessary code is a waste. It can even confuse future-you. Ever stumbled upon a piece of code you once wrote and wondered: “what was I thinking?”. It’s even worse when you finally realise that the code is solving a problem that never actually eventuated.
We are learning more often than we realise, let alone care to admit. But acknowledging that we don’t know something, then doing the simplest thing that will work, is often the best way to move forward, learn and build something.
Let’s Wrap This Up
The principle of doing the simplest thing that will work can be applied by both an architect flying at seventy thousand feet, or a newly-graduated developer deep down in the weeds. Timeframes and abstraction levels can vary, but the principle remains the same.
This principle won’t eliminate all complexity from your life, nor will it eliminate the need to use your noggin. However, it will improve your ability to find the balance between over-engineering and under-engineering the solution to a particular problem.
So when I ask developers “What’s the simplest thing that can possibly work?”, I’m asking them to look for this equilibrium point (even if I don’t really understand the discussion that follows). I’m also asking them to be the best developers they can be. Life’s too short for unnecessary complexity, especially if it’s because we can’t admit to ourselves that we don’t know something. The world is already complex enough. Let’s not make it worse whilst we’re here.