Refactoring

Method

Changing the structure of code while leaving its visible behavior exactly as it was, so the next person to touch it can read it.

Why it exists

A product where every feature works can still get slower to change month after month. A change that took two days at the start takes five days half a year later. Code kept accumulating and never got tidied, because tidying had no name and no place on a schedule.

At Pagedeck it went like this. Mia asked for the wording on the free plan notice to be changed, and Theo quoted three days. The same sentence had been copied into four files, so all four had to be found and edited, and checking whether one had been missed meant opening screens one at a time. To Mia it was a one-line request; to Theo it was three days of work; and there was no vocabulary for the gap between those two readings.

As long as the only recognized kinds of work were adding features and fixing bugs, changing structure alone could not be put on a schedule. Naming it refactoring made the terms explicit: nothing on any user-facing screen may differ before and after the work. Because the screens are promised to stay the same, the risk has a boundary, and work with a bounded risk can be given a slot.

How to run it

Put the means of proving that behavior did not change in place first. Then five steps.

  1. Check whether the area has tests. A unit test feeds one function a value and checks the result against an expected one, and with those in place you can know within a minute whether behavior still matches. If there are none, writing tests comes before any restructuring.
  2. Pick one place to work on. The signal to go by is a code smell — not a failure, but an indication that the structure has gone bad. A function that has grown to 400 lines, or the same calculation copied into four files. Neither one breaks anything today, and both make the next change cost more than it needs to.
  3. Change one small thing, then run the tests. Rename something so the name says what it does, lift one block out of that 400-line function into a named function of its own, pull four copies of a calculation into one place. That last one is repayment on the DRY principle, which says not to keep the same piece of knowledge in two places.
  4. Keep the commits separate from feature changes. When structural edits and behavioral edits arrive as one lump, the reviewer has to work out which of 500 lines is the one that changes a screen. Split them and the first part gets skimmed while the second gets read closely.
  5. Confirm at the end that nothing on screen differs. Tests green and screens unchanged is the whole finish line.

Keeping each step small is the technique that makes this work. Change thirty places at once, get a red test, and you are back to hunting for which of the thirty caused it; reverting at that point costs a day. When a step is five minutes long, reverting costs five minutes.

When it fits and when it doesn't

The value is highest in code you are about to touch. If you are opening the same file for the third time this quarter, or the next feature is going to sit on top of that structure, the cleanup pays for itself immediately. Files that keep producing the same kind of bug are good candidates too. Three bugs in six months all tracing back to the same date-handling block says the block is the problem, not the three fixes.

Three situations where it does not fit.

  1. Code that is about to be deleted. Tidying a screen that disappears in two months never earns the time back.
  2. Work that has to change behavior. That is a feature change, and what needs verifying is different.
  3. No tests and no time. Restructure without a way to check and screens break quietly, which usually means a customer finds it after the deploy.

The real limitation is that the result is invisible. Ship a feature and something new appears on screen; spend a day tidying and the outcome looks like nothing happened. That makes the time hard to get allocated. What teams without allocated time use instead is the boy scout rule: leave any file you opened for other reasons slightly better than you found it, which improves the state gradually without a cleanup budget.

How it sounds at work

"I'm booking a day this sprint to refactor the payments module. Behavior won't change."

Asking for a slot on the schedule while stating the boundary of the risk.

"This change mixes refactoring with a new feature. Please split it in two."

There is no way to tell which lines alter a screen, so the review is hard to do.

"There are no tests here, so I can't refactor yet. I'll write tests first."

There is no way to check that behavior stayed the same after the structure moves.

After the free plan wording incident, Theo booked a day and cleaned up that area. The sentence scattered across four files moved into one place, tests went on, and the commits contained nothing but structural change. The screens were identical before and after, and the next wording request took twenty minutes.

Mia now puts cleanup work on the schedule each quarter. Theo brings which file he wants to tidy, why, and an estimate, and together they check whether that area is one the team will actually touch next quarter.

What people get wrong

The most common mistake is treating refactoring and rewriting as the same thing. Both end with code that looks different, so the line between them is easy to miss. Refactoring repeats small steps under the constraint that behavior stays fixed; a rewrite drops that constraint and builds again from scratch. The risks are not comparable. A rewrite means two months where nobody can say when or how the screens will change, plus the job of carrying every bug already fixed in the old code over into the new. Blur the words and a rewrite gets approved under the name of refactoring.

The second is reading it as performance work. Cleaner structure sounds like it should run faster, and sometimes the tidied version runs slower. Splitting a function adds calls; consolidating duplicated logic adds a hop. What refactoring aims at is not execution speed but the speed at which people can change the code. A function split into five smaller ones may run a fraction of a millisecond slower and still cut the time to change it from an afternoon to ten minutes. If execution speed is the problem, measuring where the time goes is a separate job that comes first.

Easily confused with

TermWhat it isHow it differs
Technical debtThe cost that accumulates from cleanup that was put offIt names the state. Debt is the accumulated result; refactoring is the work of paying it down

Where to go next

Last updated 2026-09-16