The Perfect Code Illusion: Why Good Enough Is Usually Better
Every developer has a codebase they're ashamed of. The secret is that perfect code is mostly a myth — and chasing it is often the actual problem.
The Perfect Code Illusion: Why Good Enough Is Usually Better
Somewhere in every developer's career, they encounter code that makes them wince. A function that does six things. A variable called data2. A comment that says // fix this later with a date from 2019. A module that was "temporary" and is now load-bearing infrastructure.
The first response is judgment. The second, more mature response is empathy. And the third, hardest response is to genuinely grapple with the question: was this actually wrong?
The Cost of Imperfection Is Real
Let me be clear before I argue the other side: bad code has real costs. Code that is hard to read is hard to change. Code that is hard to change attracts workarounds. Workarounds accumulate. Eventually, the codebase is a calcified mess where every change takes twice as long as it should and introduces twice as many bugs. Technical debt is a real thing that real companies collapse under.
So the goal is not to write bad code. The goal is to think clearly about what "good" actually means in context.
What "Clean Code" Actually Costs
Clean code — well-named, well-structured, with clean abstractions and no repetition — takes time. It takes time to write, and perhaps more importantly, it takes time and understanding to design. You need to know what the code needs to be before you can design it cleanly. Often, you don't have that knowledge yet.
This is the dirty secret of premature abstraction: the abstraction that looks elegant when you build it is frequently the wrong one. You pull out a shared function because two things look the same. Then a third requirement comes in and they're no longer the same, and now you're working around your own abstraction. The duplication you so efficiently eliminated has turned into a tangled dependency.
"Three strikes and you refactor" is a more defensible rule than "don't repeat yourself." Let things repeat until you understand the pattern well enough to abstract it correctly.
The 80/20 Rule of Code Quality
In most codebases, about 20% of the code changes frequently and matters a lot. This is your core business logic: the code that makes your product different from someone else's product. This code should be well-designed, well-tested, and well-documented. Invest here.
The other 80% — glue code, configuration, boilerplate, infrastructure scripts — changes rarely and doesn't need to be beautiful. It needs to work. The fact that your Dockerfile is not a work of art is not a crisis. Expending significant energy making it a work of art is a crisis, because that energy has an opportunity cost.
AI tools have made this more useful to talk about, not less. One of the highest-leverage uses of AI-assisted coding is letting it write the 80% — the stuff that needs to exist but doesn't need to be precious. Generated code that works is better than hand-crafted code that doesn't exist yet because you haven't had time to write it.
The Refactoring Argument
"We'll refactor it later" is a joke in most engineering organisations. It never happens. The backlog grows, priorities shift, and the thing that was going to be cleaned up last quarter is the thing you're carefully working around next year.
This is true, and it's a reason to write things reasonably well the first time. But it's not a reason to delay shipping while you achieve theoretical perfection. The refactoring you do when you understand the problem better — when you've seen what the code actually needs to do in the real world — is more valuable than the refactoring you do speculatively upfront.
There's also something important about pace. A codebase that ships regularly gets feedback regularly. Feedback shapes what matters and what doesn't. A codebase that ships slowly because every feature is an exercise in craftsmanship gets feedback slowly — and often discovers, eventually, that it built the wrong thing beautifully.
What "Good Enough" Actually Means
"Good enough" does not mean "as sloppy as possible." It means: appropriate for the context, the stakes, and the timeline.
A medical device has different standards than a personal blog. A feature that will be used by ten people has different standards than one used by ten million. A proof-of-concept has different standards than production infrastructure. Context is everything.
The questions to ask are: Is it correct? Does it work as intended? Is it readable enough that the next developer can understand what it does and why? Is it testable? Does it fail safely?
If the answer to those is yes, you are probably done. The impulse to keep polishing, to find the perfect variable name, to extract one more function — that impulse has real value, but it has diminishing returns, and the returns approach zero faster than you think.
AI and the Perfectionism Trap
Interestingly, AI coding tools have a complex relationship with perfectionism. They can generate a clean-looking first draft very quickly, which makes it tempting to treat that first draft as a finished product. They can also suggest endless refactoring when you ask for improvements.
The discipline is the same as always: know when you're done. The definition of done hasn't changed because the typing is faster. The standard should still be: correct, readable, tested, and appropriate for the context.
Perfect code, in the sense of code that admits no further improvement, doesn't exist. The best code you can write today will look naive to you in five years. That's not a failure — it's growth. Ship the good-enough thing. Learn from it. Write something better next time.
The codebase you're embarrassed by isn't evidence of past failure. It's evidence that you've improved since you wrote it. That's actually something to celebrate.
Jako Heiberg
Software developer with 40+ years of building things that work. Full-stack, FastAPI, React. Based in Cape Town, working remotely, worldwide.