Always Be Refactoring

· 4 min read
refactoring practices maintenance

The Big Rewrite That Never Comes

I’ve lost count of the refactors I’ve been promised.

“We’ll clean this up after launch.” Launch happens. Cleanup doesn’t.

“Next quarter we’ll tackle the technical debt.” Next quarter turns up with its own fires.

“We need a dedicated refactoring sprint.” The sprint gets cancelled because someone urgently needs a dashboard.

Meanwhile the code gets worse. Every compromise compounds like interest on a credit card nobody’s paying off. The rewrite gets bigger, scarier, more expensive, until eventually somebody does the maths and decides it’s cheaper to just live with the mess. And that’s how the mess becomes permanent.

This is Fine dog sitting in a burning room

The problem isn’t that people are lying when they promise the big refactor. They mean it at the time. It’s that a large refactor needs a large uninterrupted block of time, and large uninterrupted blocks of time do not exist in commercial software. There’s always something more urgent. There will always be something more urgent. Planning around a stretch of calm that never arrives isn’t a plan, it’s a wish.

So the alternative is boring. Constant small refactors. Unglamorous, relentless tidying. Nobody gets promoted for it and nobody mentions it in a retro. It just quietly works.

Leave It Better Than You Found It

The Boy Scout Rule, and honestly it’s the whole thing. Every PR, one small improvement beyond what you came to do.

Rename the variable that confused you for thirty seconds. Extract the method you had to read three times. Delete the commented-out block from 2019 everyone’s been scrolling past. None of these are worth a ticket. All of them are worth doing while you’re there.

The trick is keeping it genuinely small. If your “small improvement” adds four hundred lines to the diff, you’ve stopped tidying and started a project, and your reviewer is going to hate you for it.

While You’ve Got the Context Loaded

This is the bit that makes it cheap.

When you’re already changing a function, you’ve done the expensive part. You’ve read the surrounding code, worked out what it does, worked out why it does it that way, and built a mental model of how it all fits together. That model took twenty minutes and it’s sitting in your head right now.

Tidying while you’re in there costs almost nothing. Coming back in three weeks means rebuilding all of it from scratch, and by then you’ll have forgotten which bit annoyed you in the first place.

So do it now. You’re already holding everything you need.

Tests First, Or You’re Just Gambling

Refactoring without tests isn’t refactoring. It’s rewriting code you don’t fully understand and hoping.

Behaviour-preserving changes need something that tells you the behaviour is still preserved. If that something doesn’t exist, write it before you touch anything. Yes, it’s slower. It’s also the difference between confidence and closing your eyes.

And if the code is genuinely untestable, well, there’s your refactor. Make it testable first, then improve it.

Keep the Commits Honest

Separate your commits. One for the behaviour-preserving cleanup, one for the actual change.

Mixing them produces a diff where nobody, including you, can tell which line broke it. And something will break it. When it does you want to bisect down to a commit that either says “renamed things” or “changed behaviour”, not a Frankenstein PR that’s half feature and half tidy-up.

Reviewing a PR that mixes refactoring with features

When Not To

I’m not going to pretend any of this is free.

Feature pressure is real. If you’re always refactoring you’re never shipping, and at some point somebody with a spreadsheet starts asking uncomfortable questions. Sometimes you genuinely do take the compromise now and pay for it later. That’s a legitimate call, as long as it stays a call and doesn’t quietly become a habit.

Refactoring also takes skill. Someone “improving” a module they don’t understand can absolutely make it worse, and do it confidently. If you can’t articulate why the current shape is wrong, you’re not ready to change it. Leave it alone and go and read it again.

There’s a real cost to churn too. Constant refactoring means constant merge conflicts, constant relearning, constant “why has this moved?” in review. Code that sits still, doing its job, being boring, has value as well.

The goal isn’t perfection. It’s just not letting things get worse.

Which is a low bar. It’s genuinely surprising how few teams clear it.

How to Actually Build the Habit

  1. Every PR, one improvement beyond the thing you came for
  2. Every bug fix, tidy the code around the bug while you’re in there
  3. Every review, suggest exactly one refactor. Not five. One
  4. Keep cleanup commits separate from behaviour commits
  5. If it needs a ticket, it’s too big. Split it or schedule it properly
  6. Never refactor code you can’t test. Make it testable first

The cleanest codebases I’ve worked on didn’t have refactoring sprints. They had people who tidied up after themselves, one commit at a time.

Thumbs up

Until next time, happy coding!

Available for rescue and re-platforming work

I take over platforms that already exist and are in trouble. Node, TypeScript, React and Laravel, mostly in regulated or high-traffic environments. If something needs rescuing, re-platforming or finishing, my full history is on the CV.

Related Posts

Stone Soup Programming

collaboration teams

Put a rock in some water and ask if anyone fancies adding a carrot.

Comments