How often have you found yourself at the tail end of a project, waiting for the day that you can finally get sign-off on the deliverables from the client, to attain that point of perfection and go live, but that day never seems to come?
Each new day seems to bring another handful of bugs, or tweaks to feature requests which you thought had been fully pinned down.
We all like to make sure we put as much effort as we can into our work, to reach some perfect ideal of quality. But at the same time, we fear that our efforts are in vain, and that the law of diminishing returns will strike and keep us from reaching that moment of completion.
It’s not easy to find, but there is a point of balance for any project, where deliverables can be weighed against resources, and a compromise reached to find a clean path towards a smooth delivery. A point at which you’ve put in just enough effort to reach the deliverable.
But when do you know when enough is enough?
How do you balance time and effort, project quality and budget, team resources and deadlines to ensure that the final product hits that sweet spot of success?
The answer is to produce work that’s not very good…
Now wait, that doesn’t sound right. Does it?
This involves some rethinking of how you’d normally judge the quality of a final deliverable, and when you’d consider it “done”. I’ll explain…
Calling It Quits
There’s one extreme school of thought which assumes that only something that is perfect is ready for release. Push hard for completely bug-free code. Don’t let a single line out of the door until it’s provably flaw-free. Test, test and test again until everyone is completely satisfied.
But what is perfect? Is it the point where all bugs are squished, and a product is flawless? Is it when the client is happy, beyond any shadow of a doubt, that their product will never encounter a serious issue?
At what point do you say a project is done, and prepare to launch.
How do you know when to stop?
Unless you’re an engineer working on a piece of mission critical code for a spacecraft or nuclear reactor, it’s likely that you don’t need to cover for all edge cases.
And even if you attempt to create code which can be shown to be 100% free from defects, you’re likely to run into the same problems - an unattainable goal which always seems tantalisingly within reach, but never quite so.
For example, attempts to create code which is formally verifiable can often result in unecessary extra work and complexity. And even then, you may find that your efforts are ultimately wasted.
“After all this effort, either carefully designing specs that are amenable to push-button verification, or tediously writing proofs in Coq, what do we get? You’d hope for perfect code, but the truth is a lot less palatable.” - Ray Wang, Formal Verification: The Gap Between Perfect Code and Reality
The worst case scenario for a space mission is complete destruction. Several other scenarios would result in some form of critical systems being damaged beyond repair, potentially stranding an astronaut beyond help. These are all-or-nothing outcomes. They need to be catered for fully.
The worst case scenario for something like a website or ecommerce platform may be that a customer’s experience is subpar, or some data is lost, or a customer is billed a wrong amount. No-one is going to end up stranded in the freezing cold depths of space over this.
A fault in an ecommerce website is serious enough, for sure - but through careful planning we can identify these worst case scenarios and aim to reduce their impact, not control them completely.
It may be sufficient to know that if something like a data loss occurs, there is a suitable backup to restore from, or if a customer is billed incorrectly, suitable systems are in place to allow for a quick adjustment and analysis of the error. It’s not necessary to have complete certainty that no data loss will ever occur or that financial problems can never happen.
We need not create code which functions perfectly in all scenarios. We only need to create code which works well enough to satisfy a suitable number of the users, a suitable amount of the time.
The principle in action is that things don’t always need to be very good. They only need to be good enough.
Fighting Against Instinct
At first, some people may struggle to accomodate this way of thinking. Perhaps it’s been drummed into them from a young age that winning is everything, or that perfection is attainable.
But I believe this is a harmful attitude. Life is not all about winners and losers. A runner in a marathon may finish in last place. But they finished, and maybe it was the first marathon they had entered.
Next time they’ll do better. But this time, they achieved their goal.
To a lot of people, “only just good enough” sounds like an oxymoron at first.
- Only just
- Good enough
“Good enough” implies - well - good enough, clearly. It’s the point at which a product is good enough to go live. It’s the point at which a site is good enough for the public to access. It’s the point at which a project is good enough for the client to sign off on.
“Only just” … now, that sounds like a sacrifice in quality, surely?
How often have you heard:
- That’s very polished code, clearly a lot of effort has gone into that
- That’s very clear UI, you’ve worked hard
- That’s very fast, I’m impressed
Maybe this is a sign of pride in some, I know I feel that way sometimes. I might spend hours on a single piece of code, refining it and tweaking it to reach some optimal level of performance. And I’ll feel good about that.
But to what end? Who does this benefit, this early and self-contained optimisation?
Consider how much time has gone in to reaching that point. Maybe I spend an extra 50% of my time optimising a piece of code. Does that translate into a 50% rise in sales? A 50% better customer experience of that feature? Have I, ultimately, wasted my time by trying to optimise for myself, and not my customers?
Let’s change these phrases around, and get rid of our unnecessary Very Goods.
Taking the three phrases above, we could just as easily want to see:
- That’s good code, it does the job. It’s good enough for me
- I like that UI, it’s functional and I understand it. It’s clean enough
- The site is pretty speedy. Maybe there’s room for improvement, but, yeah, it’s fast enough
Here, we may have spent only half the time working on the features, not pushing ourselves to aim for that elusive sense of perfect, and reached something which is just good enough in the client’s eyes.
But, none of this is to say that we can’t eventually reach “very good”. Only that we should be aiming to hit that point when it makes sense. Not too early, and not with excessive stress and strain.
But what of that unoptimised code? There’s still room for improvement, surely? That’s a chunk of code that isn’t working as well as it could.
Take the earlier example - maybe we’ve not completely eliminated the chance of data loss. There’s not a 100% certainty that data won’t be lost, maybe we can say with assurance that it’s 99.5%.
But we spent half the time getting to 99.5% that it would take to reach that 100%. And in that extra time, we could build the failsafe systems which can react to any potential data loss, and which we might not have in place to catch us if our 100% assurance happened to be wrong (which there’s a very good chance it would be).
From the end-user’s perspective, maybe there is room for improvement at the point a project goes live. But, by launching when the project is good enough, you allow for the user to guide the project development, and not just the client or yourself, the developer.
Finishing the marathon is the goal here. Not to win. Plan to finish, within an allocated timeframe and budget. But first, you must find the finishing line.
How To Identify “Just Good Enough”
Another agile concept which I like to make use of during project planning is the idea of user stories
This planning phase is designed to view the project from the point of view of the actual users of the system - how it will ultimately be used, the ins-and-outs and the daily, real issues that its users will encounter.
This involves building up a number of individual stories, each of which comes with extra detail including conversations carried out by the development team and, crucially, acceptance criteria which define when the story will be done.
This is key. Having a solid map and route in place from the start. And knowing how to draw that map is just as important as what the map contains.
Once you have that map, you know how much effort you need to spend in getting from A to B.
If you were driving a car from your hometown directly to another city, you wouldn’t take a winding route through every other town along the way.
You don’t need to see those other places, you only need to get to your destination.
The same with project implementation. You don’t need to optimise for everything as you go. You only need to make sure that you arrive at your previously defined acceptance criteria in good time and within budget.
Of course, things aren’t always that simple. You initially need to make sure that you have sufficient stories to cover your project. Once the build is underway, you also have to ensure that these stories are managed well, the correct developers are assigned to work on the correct stories, deadlines are met and so on.
Also, during the development, test suites are set up to provide automated processes which can verify the acceptance criteria - various approaches are available, including unit testing, behaviour-driven development (BDD) and test-driven development (TDD).
Ultimately, this is about expectations and determining the most efficient way to reach them, and there’s no better way to do that than to pin down, right at the start, what those expectations are, and how to meet them.
Acceptance criteria define “Just good enough”. Once you know what needs to be done, you can do that, and then no more.
You don’t need to waste time working on “very good” if you don’t need to be very good.
But What About Being The Best?
I’ve seen arguments against this philosophy, that bring up examples of times when projects failed, or expectations weren’t met and it wasn’t “good enough”.
There were other further suggestion that the proper way would be to reach for some idealistic form of perfection.
Especially in the world of business, you may want to be seen as “better” than your competitors. Harder working, faster code.
But what your clients want are results. And results are what you can control from the start. Plan out what your results will be, and ensure you meet them. That’s your “good enough”, and a good client will value that.
It’s a self-referential concept though - things are only “just good enough” when they’re “just good enough”. Sounds obvious. Yet, at the same time, not.
If you’re not getting enough sleep to get by, then that’s not good enough. If your code isn’t of a high enough standard to meet some acceptance criteria then it’s not good enough.
“Not good enough” happens when time has not been spent beforehand determining what “good enough” is, and how to get there.
It’s something for you, as a developer, as a team, to define. You are the master of what is (and isn’t) just good enough. The term itself is malleable too. You should adapt it to work with your own philosophy - if you’re uncomfortable with the notion of only just meeting criteria, why not say “It’s good enough for us”?
“In my experience, a model is just barely good enough when it fulfills its purpose. You must know why you’re creating a model and ensure that it meets its goals.” - Scott W. Ambler, Just Good Enough
Figuring out the sweet spot for your project isn’t just about the health of your project. It’s about your own health too.
The Emotional and Physical Costs of “Very Good”
This might all sound like nothing more than motiviational jargon. Wistful thinking for an idealistic environment.
Though in a way, that may be a valid point - Agile methodology like this has been argued over, in one form or another, for almost as long as modern managerial practices have existed and is often seen as overkill, especially for smaller projects.
But it has foundations which contain deeper truths. At its heart, it aims to address the very real issues that face developers on a daily basis.
If a product is delivered to a very high standard, but along the way there have been endless late nights, stress, disagreements and team fractures then that is not good enough, particularly in the long run.
Maybe you find that you can deliver very high quality code in tight timeframes and suffer no ill effects. But then that is your “just good enough”. It changes from person to person, team to team.
Project planning is not just about sketching out code structures and flow charts, it’s also about managing the human components to ensure that the people working on a project do so in the most efficient way possible.
And more often than not, that involves tight control over the quality of their work, not the quantity.
After all, why try to deliver 110%, when 95% is all you really need?
A Healthy Attitude To Project Work
“Just good enough” is not about sacrificing the quality of deliverables in favour of slacking off.
It’s about finding your own balance, determining what level of work is most optimal for you to gain the greatest result, and benefiting from this both professionally and personally.
It’s a philosophy not just for a place of work, but also for peace of mind.
So why push for perfection? Your time is valuable, and you can invest it elsewhere and in your own personal development if you simply let your work be just good enough.