We estimated a refactor at two weeks based on how much code needed to change. It took five, and almost none of the overrun was the code itself.
Where the time actually went
Rewriting the affected module took roughly the two weeks we estimated. The other three weeks went to tests that asserted internal structure rather than behaviour, and to the fact that the change couldn't be deployed in one step without a period of the old and new code paths needing to coexist.
Tests that fight the refactor
A test that checks a function calls another specific function, rather than checking the outcome, breaks the moment you reorganise, regardless of whether the behaviour is still correct. We found a cluster of tests like this covering the module we were changing, and each one needed to be rewritten to test behaviour before we could trust the suite's answer about whether the refactor had broken anything.
The deploy problem nobody scoped
The module in question was read by two other services during the transition. We couldn't cut over all three at once without a coordinated release window none of the teams wanted to commit to, so the refactor needed a compatibility layer supporting both the old and new shape simultaneously, used by nobody for more than a couple of weeks, then removed.
- Behavioural tests are a precondition for refactoring, not a nice-to-have — if the suite tests structure, it will actively fight a change that improves the structure.
- A refactor that touches a shared boundary is really two projects: the refactor itself, and a migration plan for everyone reading that boundary.
- Compatibility layers need an explicit removal date, or they become a fourth permanent code path nobody planned for.
A refactor is not blocked by the code being hard to change. It's blocked by everything around the code that assumed it never would.
What we'd estimate differently next time
We now ask, before quoting a refactor: does anything outside this module observe its current shape, and do the existing tests assert behaviour or structure. Both questions take an hour to answer and would have told us the true five-week estimate before we committed to the wrong one.
The part that was genuinely satisfying
Once the tests were rewritten and the compatibility layer was in place, the actual refactor took exactly as long as estimated. The estimate wasn't wrong about the code. It was silent about everything the code was entangled with.
