
I once spent two days making a function "clean" that I deleted the following week.
It was beautiful. Perfectly factored, every responsibility split out, abstractions for cases that might someday appear. It was also for a feature we ended up cutting. Two days of my life, polished into something nobody ever ran in production.
I'm not anti-clean-code. Clean code is a genuine good. But somewhere along the way, "clean" stopped being a tool for shipping better software and became a thing I chased for its own sake — and it was quietly killing my momentum. If you've ever refactored instead of finishing, this is for you.
Clean code becomes a momentum-killer when you treat purity as the goal instead of a means to shipping maintainable software. Polishing code before you know if it'll survive, abstracting before you have real duplication, and refactoring working code that nobody's touching all burn time you could spend delivering value. The fix is matching your code quality to the code's actual stakes and lifespan.
The whole point of clean code is to make software easier to change and cheaper to maintain. That's it. It's instrumental.
The trap is forgetting the "so that." We polish code so that it's easier to modify later. But if the code is never modified, or never even ships, the polishing produced exactly zero value. You optimized for a future that never arrived, on the company's time.
I'd internalized "clean code is good" so thoroughly that I stopped asking the only question that matters: clean for what? A throwaway script and a payment system are not the same code, and they should not get the same care. Learning to spend effort where it actually pays off is a recurring thread in the brutal truth about becoming a senior developer — judgment about what not to polish matters as much as the polishing itself.
Clean code is in service of shipping. The moment it becomes the goal, it starts working against you.
Photo by Ilya Pavlov on Unsplash
The single most expensive "clean code" mistake I made over and over was abstracting too early.
You see two things that look similar, and the clean-code reflex screams "don't repeat yourself!" So you build an abstraction to unify them. Then the third case shows up and it's just different enough to break your abstraction. Now you're bending real requirements to fit a structure you invented before you understood the problem. The abstraction that was supposed to save effort is now the thing fighting you.
The honest rule I follow now: a little duplication is cheaper than the wrong abstraction. I let things repeat two or three times until the real pattern reveals itself, and only then do I unify it — based on what the code actually does, not what I guessed it might do. Martin Fowler has written about how the wrong abstraction is often more expensive to live with than the duplication it was meant to remove, and that matches every scar I have on the subject.
What this looks like in practice:
Not all code deserves the same care, and pretending it does is how you waste your best hours.
I now sort code by its lifespan and blast radius, and spend accordingly:
| Code type | How much polish |
|---|---|
| Throwaway script, run once | Almost none — make it work |
| Internal tool, low stakes | Readable, don't over-engineer |
| Core product, long-lived | Genuinely clean and tested |
| Payment / security / data | Maximum care, no shortcuts |
This isn't an excuse for sloppiness on the things that matter. It's the opposite — it concentrates my real effort where it pays off. By not gold-plating the throwaway script, I have the time and energy to do the payment code properly. Spreading equal polish across everything means under-investing in what's critical and over-investing in what's disposable.
Photo by Annie Spratt on Unsplash
Momentum is a real, fragile resource, and over-polishing drains it.
There's a particular trap where you keep refining instead of finishing. The feature works, but it's not clean enough yet, so you keep going — extracting one more function, renaming one more thing, adding one more layer. Meanwhile it hasn't shipped, users don't have it, and you have no feedback on whether you built the right thing at all.
Shipping working code and improving it with real feedback beats polishing in a vacuum. It's the same instinct behind learning to ship faster by writing less code — the value is in the problem solved, not the volume of polish around it. You learn what actually needs to be clean only after the code meets reality. Half the abstractions I lovingly built before shipping turned out to be cleaning the wrong things, because I didn't yet know where the real pressure would land.
The discipline isn't "never clean up." It's "ship, learn, then clean the parts that earned it."
To be clear, there's a category where I never cut corners, and you shouldn't either.
Code that's central, long-lived, and high-stakes deserves real care from the start — the core of your product, anything touching money or security, the modules ten other things depend on. Here, messiness compounds fast and the cost of getting it wrong is enormous. This is exactly the code that pays back every hour you invest in keeping it clean.
The whole point of being pragmatic about the disposable stuff is to have the resources to be uncompromising about this. Pragmatism isn't lowering your standards everywhere. It's spending your standards where they matter and refusing to waste them where they don't. That balance — not purity, not sloppiness — is what keeps momentum and quality alive at the same time.
There's a darker version of this I had to be honest with myself about, and you might recognize it too.
Cleaning code can be a sophisticated form of procrastination. The hard part of most features isn't the code — it's the ambiguous decision underneath it, the thing you're not sure how to solve. Refactoring is comfortable. The rules are clear, the feedback is immediate, and at the end you feel productive. So when I hit a genuinely hard, uncertain problem, I'd sometimes notice myself drifting toward "let me just clean up this nearby function first." I wasn't cleaning because it needed it. I was cleaning to avoid thinking about the scary part.
It looks like work. It feels like work. It even produces nicer code. But it's avoidance with a clean conscience, and it can eat days while the actual problem sits untouched.
The tell, for me, is timing. If I find myself reaching for the refactor right when the real work gets hard, that's not craftsmanship — that's flinching. The fix is embarrassingly simple: name it. "Am I cleaning this because it needs cleaning, or because I don't want to face the hard thing?" Usually I know the answer the moment I ask.
Refactoring is real work. But refactoring instead of solving the hard problem is just procrastination wearing a tidy sweater.
Clean code is genuinely good. But like any good thing, it can become the comfortable place you hide from the work that actually moves the project — and that's the quietest momentum-killer of all.
After enough of these lessons, I boiled my whole approach down to a single question I ask before polishing anything: how long will this code live, and how much breaks if it's wrong?
That's it. Those two dimensions — lifespan and blast radius — tell me almost everything about how much care to spend. A script I'll run once and delete has a lifespan of minutes and a blast radius of nothing, so I make it work and walk away. The function at the heart of our billing flow will live for years and breaks money if it's wrong, so it gets every ounce of care I have. Most code lives somewhere between, and I aim for "clean enough that the next person isn't cursing me," no more.
The mistake I used to make was applying the billing-flow standard to the throwaway script, and then running out of energy by the time I reached the code that actually deserved it. Treating all code as equally precious isn't rigor. It's a failure to prioritize, dressed up as discipline.
Spend your craftsmanship like money. Lavish it where the stakes are high, and stop apologizing for being frugal where they aren't.
When I started matching care to consequence, two things happened at once. My critical code got better, because I had the energy to do it right. And my momentum came back, because I stopped pouring hours into polishing things that didn't need it and might not survive the week. That balance — not maximum cleanliness, not sloppiness, but care proportional to stakes — is the whole answer.
If matching effort to stakes resonates, it's worth following along for more field notes on the unglamorous judgment calls that quietly separate shipping from spinning.
Q: Are you saying clean code doesn't matter? Not at all. Clean code matters a lot — for code that's long-lived and high-stakes. The argument is to match the effort to the code's actual importance, not to apply maximum polish to everything regardless of whether it'll ever be read again.
Q: How do I avoid premature abstraction? Tolerate a little duplication. Don't unify two similar pieces of code on sight — wait until you have three real uses and the genuine shared pattern is obvious. A bit of repetition is far cheaper to fix later than a wrong abstraction.
Q: When should I stop polishing and ship? When the code works, handles its real cases, and is clean enough for its stakes. If you're refining a working feature purely for elegance while users wait, you've crossed from value into vanity. Ship, get feedback, then clean what reality shows you needs it.
Q: Doesn't shipping less-than-perfect code create debt? Some — but unshipped perfect code creates 100% waste if it's wrong, and you can't know if it's right without feedback. Intentional, tracked roughness on low-stakes code is cheaper than polishing the wrong thing in isolation.
Clean code is a tool, not a trophy. It earns its keep on the code that lives long and matters most — and quietly steals your momentum everywhere else, through premature abstraction and polish nobody asked for.
Ship the work. Clean what reality proves needs cleaning. Spend your standards where they pay off, and stop polishing things you might delete next week.
Where are you chasing clean code that's actually just keeping you from shipping?
I spent years saving the hardest task for when I 'felt ready.' Doing it first instead quietly fixed my focus, my dread, and my output.

I tracked every distraction for a week and was horrified by what I found. Then I fixed the three that mattered most.

No following, no network, no luck. Just an unglamorous system I ran for eighteen months. Here's exactly what I did.

Comments
Sign in to join the conversation
No comments yet. Be the first to share your thoughts!