A piece of code usually doesn’t become a “legacy system” overnight. The more common path is this: the first temporary branch solved an urgent need, the second branch copied that logic, and a third team, unaware of the original conventions, added yet another entry point. Each step looks reasonable on its own, but together they make changing anything dangerous. I’ve seen this path repeatedly in systems I’ve maintained for years—it is almost never a single moment of “bad writing,” but local changes that bypass shared boundaries again and again.
What Decays Is Shared Understanding
Inconsistent formatting and poor naming certainly raise the cost of reading, but what is more serious is boundary failure: state modified by several modules at once; similar rules written in three different places; callers forced to know internal details they shouldn’t have to know.
Suppose a booking system starts with only “create” and “cancel.” Later, rescheduling, waitlisting, bulk import, and manual correction are added one after another. If every new flow directly modifies inventory, notifications, and order status, it is fast in the short term, but in the long run no one can answer “which action triggers a notification.” The problem with the system is not the number of files, but that the rules have lost their single home.
Speed Is Not the Opposite of Design
“The business moves fast, so there’s no time to design” is often true—but that doesn’t mean consistency must be abandoned. Design isn’t about drawing a permanently correct diagram before you start; it’s about answering at least these questions before every change: Who owns this rule? Will a new branch alter existing paths? How do we isolate the uncertain parts?
During periods of rapid change, what is most worth preserving is usually not a complete abstraction, but a few minimal constraints: clear ownership, traceable entry points, a handful of tests on critical paths, and exceptions whose rationale can be articulated.
Slow Down with Systems, Not Heroics
The value of code review is not catching punctuation errors, but making the author explain boundaries; the value of tests is not chasing coverage, but protecting behavior that must not be silently changed; the value of refactoring is not making things “prettier,” but eliminating the structure that would otherwise be copied next time.
There’s no need to promise to renovate all the legacy code. First find the one chain that is changed most often, fails most often, and blocks collaboration the most, and give it rules, ownership, and verification. Code will never stay clean forever, but a team can keep it from losing its comprehensibility so quickly.