Engineering Practice
You Can't Rewrite What You Haven't Mapped
Before a 25,000-line undocumented rules engine could be rewritten into something modular, someone had to reconstruct what it actually did — label by label, exit code by exit code. The undocumented behavior of the old system is the real specification, and skipping the mapping step is how rewrites quietly change behavior.
- Engineering Practice
- Legacy
- Refactoring
- Architecture
A team I worked alongside wanted to replace a sprawling, undocumented rules engine — around twenty-five thousand lines of a domain-specific language across dozens of files — with something modular and maintainable. Reasonable goal. But the first real work wasn’t writing the new system; it was reconstructing what the old one actually did: tracing the flow label by label, cataloguing every exit point and its code, mapping which config files fed which decisions. That mapping was tedious and unglamorous, and it was the thing that made a safe rewrite possible at all. The principle it taught me: you cannot rewrite what you haven’t mapped, because the old system’s behavior — written down nowhere — is your actual specification.
The legacy system is the spec, like it or not
When you replace a system that’s been in production for years, the requirement isn’t “do something reasonable.” It’s “do what the old thing did,” including the parts nobody remembers deciding. Every weird branch, every special-case exit, every quirk that some downstream consumer now silently depends on — that’s all behavior the rewrite has to either reproduce or consciously change. The catch is that for an undocumented system, this specification exists only as the running code. There’s no document to build against; the behavior is the document, and it’s written in a language you have to read carefully to understand.
A rewrite’s real requirements aren’t in a spec doc. They’re encoded in the behavior of the thing you’re replacing — and most of them are undocumented.
Skipping the map is how rewrites change behavior
The seductive failure mode is to skim the old system, grasp the happy path, and start building the clean new version. You’ll get the common case right and silently drop the long tail — the edge cases, the special exits, the compatibility hacks that were load-bearing for some consumer you never knew existed. Those don’t show up in a demo; they show up weeks later as “the new system handles this case differently,” which is a polite way of saying it broke something. The rewrite didn’t fail because the new code was bad. It failed because the new code was built against an incomplete understanding of the old behavior. The map is what closes that gap before it becomes an incident.
Mapping is archaeology, and it has a method
Reconstructing an undocumented system is genuinely archaeological — you’re recovering decisions from artifacts. What made it tractable was being systematic about it:
- Trace the main flows end to end, naming each step and what it does.
- Catalogue every exit point — not just “it succeeds or fails,” but each distinct outcome and the code/reason attached to it. The exits are where the real behavioral contract lives.
- Map the inputs: which config files, tables, and external lookups feed each decision, so you know what the behavior actually depends on.
- Write it down as you go, in a form the rewrite team can build against. The map is the deliverable, not a scratch pad.
The output is a behavioral specification reverse-engineered from reality — the document that should have existed all along. It turns “we think it does roughly this” into “here is exactly what it does, label by label.”
The map is also where the rewrite earns its design
There’s a bonus the mapping pays out: once you can see the whole behavior, the right decomposition becomes obvious. The monolith mixed concerns because it grew organically; the map reveals the natural seams — which behaviors are independent, which share state, where the boundaries actually are. You can’t design good module boundaries for a system you only half-understand, so the comprehension work and the design work are the same work. The map tells you both what to preserve and how to cut it apart. (Whether those pieces are truly modular is its own question — but you can’t even ask it without the map.)
Respect the comprehension phase
So when someone proposes replacing a legacy system, the honest first estimate isn’t for the new code — it’s for understanding the old one. That phase feels like it’s “not real work” because no new features ship during it, which is exactly why it gets cut, and exactly why under-mapped rewrites quietly change behavior and erode trust. Budget for the map. Treat the reconstructed behavior as the spec. Decide deliberately what to keep and what to change, rather than discovering the difference in production. It pairs with the discipline of migrating without touching the callers and choosing a repo structure that fits the seams once you can finally see them. If you’ve reverse-engineered a legacy system before rebuilding it, I’d like to hear how you mapped it.