The module everyone on that project hated was the first one I rewrote. Weeks later it was clean, typed, covered by tests, and quietly wrong in ways I could not see yet.
The ugliness had not been sloppiness. Almost every strange branch in that old code was a real edge case someone had closed years earlier: a partner who sent a field in the wrong shape, a currency rounding rule, a status that only ever appeared for one kind of account. None of it was written down anywhere. The code was the document.
So the clean version grew those branches back, one support ticket at a time. Each one showed up as a user-visible failure instead of a known special case. The real cost of a rewrite sits right there, in handled edge cases turning back into incidents, and not in the engineering weeks.
Now, before I touch an inherited system, I treat the old code as the only written spec I have, and I ask five things:
Which parts of this mess are load-bearing, and who is still around to tell me?
What inputs does it actually receive in production, not in the docs?
Which behaviors am I allowed to change on purpose, and who signs that off?
How will I notice a divergence before a user does?
What is the smallest piece I can replace and still prove it behaves the same?
Answering those probably reshapes the plan more than any architecture diagram does.
I wrote up how I turn old behavior into a replayable spec on my blog: https://polycratia.com/c/77b23256
If you have rewritten an inherited system, I would like to hear how you found out which of its quirks were load-bearing.