← The ADLC library
Live debates · 38

The refactor that pays for itself thirty times

The value of a refactor was always the saving per future touch multiplied by the number of future touches. Agents did not change the saving. They changed the multiplier, and that turns a small number of cleanups from indulgence into obvious spending.

Every engineering leader has said no to a refactor, correctly. The proposal comes with an estimate and no return, the team wants to do it because the code offends them, and the honest answer is that the module gets touched twice a year and can stay ugly.

That answer was right and for most of your codebase it still is. But the arithmetic behind it has an input that moved, and my position is that a small number of refactors have crossed from indulgence to obviously worth funding, while the large majority became even less worth doing than before. The interesting work is telling those two groups apart, and it is arithmetic, not taste.

The formula nobody writes down

The value of a refactor is the cost it saves on each future encounter with that code, multiplied by how many future encounters there will be, minus what it costs to do and the risk of doing it.

Both terms in that product used to be small. A messy module might cost a human an extra hour to work in, and it might be worked in four times a year. Four hours a year against a two-week refactor is an easy no, which is why the answer was almost always no and why the people asking learned to argue on aesthetics instead. They could not show the money because there was not much money to show.

Now consider what an encounter is. Every agent run that reads that module is an encounter. Every review of the output of one of those runs is another. In a module under active development, the number of times something forms a model of that code in a week is not comparable to the number of times a human opened it in a quarter.

The saving per encounter did not change. The multiplier did. That is the entire argument, and everything else follows from it.

Cost per encounterExtra context that must be loaded, extra time a reviewer spends orienting, extra chance of a confidently wrong assumption. Roughly unchanged.
Encounters per periodHuman opens, plus agent runs, plus reviews of those runs. This is the term that moved, and it moved by a lot in a small part of the codebase.
Cost to refactorLower than it used to be, and less trustworthy than it looks, because the refactor itself now generates code nobody has read.
Risk of the refactorUnchanged where the tests are good, worse where they are not, and worse again where the tests were generated against the current behaviour rather than the intended behaviour.
Only the second row changed materially. Any refactor argument that does not turn on that row is the same argument you correctly rejected in 2019.

Which code sits at the top of the multiplier

You are looking for the intersection of three properties, and the intersection is small, which is the good news.

High touch. Change frequency from your version control history, over the last quarter. This is the multiplier itself and it is the term that dominates. A module in the top few percent by change frequency is where all the interesting candidates live.

High context cost. How much has to be loaded to change one thing safely. A proxy you already have: the size of the change set that typically accompanies a change here. If touching one thing reliably drags four files with it, the context cost is high and it is paid every single time.

High ambiguity. Where the intent is not visible from the code. The signal is rework: changes to this area that come back. Rework attributed by area rather than by ticket is a map of where something guessed, and guessing happens where the local rules are invisible.

A module scoring high on all three is a refactor that pays back on a timescale your finance partner will recognise. A module scoring high on only one is the refactor you should keep saying no to.

Still not worth it

  • Ugly, stable, rarely touched
  • Clever but self-contained
  • Legacy behind a strong contract
  • Offends a senior engineer's taste
  • Payback measured in years of rare edits

Now worth funding

  • Touched weekly, drags four files each time
  • Rules that live in three places at once
  • Rework concentrates here and nobody can say why
  • Every run loads far more than it needs
  • Payback measured in encounters per month
Both columns contain code an engineer would describe as bad. Only one of them has a bill attached that arrives this quarter.

The number in the title

I called this the refactor that pays for itself thirty times, and I want to be honest about where thirty comes from: it comes from your own change history, not from any study, and I would not use anyone else’s figure for it.

Do the arithmetic yourself, roughly and in public. Take one candidate module. Count how many times it was touched in the last quarter, by anything. Estimate what a change there costs beyond what it would cost if the module were clean, in context loaded and reviewer minutes. Multiply. Compare to the cost of doing the work.

Rough is fine. The point is not precision, it is that the sum exists at all, because for the last twenty years the refactor conversation has been conducted entirely without one. A leader presented with a multiplier drawn from their own repository behaves differently from one presented with an aesthetic complaint, even when the underlying request is identical.

The trap in doing the work

One serious caveat, and it is the reason I would keep these projects small.

Refactoring with agents produces a large volume of new code that nobody has read, in the exact area you have identified as your highest-traffic module. You can convert a legibility problem into a comprehension problem and record it as a win, because the metrics you were tracking, context loaded and rework rate, may well improve while the number of lines in your most important subsystem that no human has ever looked at goes up sharply.

Which means these projects need the opposite of the usual staffing. Small scope, one module, and the reading is the deliverable rather than the cleanup. If the team cannot read the output, the refactor was too big.

Where this breaks down

The whole argument assumes today’s context economics persist. If loading large amounts of context becomes cheap and reliable enough that a messy module costs nothing extra to work in, my multiplier collapses and every one of these refactors becomes an indulgence again. That is a plausible near-term development and it would undo the core of this piece.

High change frequency may mean the design is right. Code gets touched a lot when it is where the business is moving. Refactoring the most active module in the system, mid-flight, is how you spend a quarter and arrive at the same place with fewer tests, and “it changes constantly” is as much an argument against touching it as for.

Rework attribution by area is soft. I have leaned on it twice. It requires judgement about which change caused which follow-up, made by people who would like the answer to be interesting. Treat it as a hint about where to look, never as a finding.

A refactor that pays thirty times still needs somebody to want it. I have written as though the constraint on cleanups is the business case. Often the constraint is that nobody on the team has the appetite or the standing to lead one, and a spreadsheet does not fix that.

And the strongest objection: the multiplier makes bad refactors more expensive too. Everything I have said about a good cleanup compounding across encounters applies identically to a wrong one. A refactor that encodes a mistaken model of the domain into the highest-traffic module in the system now propagates that mistake across every run, at exactly the multiplier I have been using to justify the work.

The takeaway

The refactor conversation has always been an argument about taste standing in for an argument about arithmetic. The arithmetic now works out in favour of a small number of cleanups, because the number of future encounters with high-traffic code went up sharply while the cost of each bad encounter stayed the same.

Find the intersection of high touch, high context cost, and high rework. Fund those, keep them small, and make reading the output the deliverable. Keep saying no to everything else, more firmly than before.

If you take one thing into next week: rank your modules by change frequency over the last quarter, take the top five, and for each one ask how much of what gets loaded to change it is actually relevant. Your refactor list is in that intersection, and it is shorter than your team thinks.