Most real engineering work happens inside code someone else wrote, often years ago, with no tests and nobody left who remembers why it's shaped the way it is. That's not an edge case to dread — it's close to the default working condition, and it has its own learnable approach.
3 min read
A common, useful definition (from Michael Feathers' work on the subject): legacy code is code without tests — not code that's simply old. Under this definition, code written yesterday with no tests is already legacy in the way that matters most: you can't verify a change didn't break it except by re-checking everything manually, exactly the same problem old, untested code has.
Encountering confusing, poorly-structured code often triggers an urge to rewrite it cleanly from scratch — but a working system, however messy internally, already encodes a huge number of real-world edge cases and decisions that were earned through actual production experience, often not written down anywhere except in the code's own behavior. A rewrite risks silently dropping those hard-won edge cases, and rewrites reliably take far longer than expected, precisely because "why does this handle things this oddly-specific way" often only becomes clear once the same edge case bites the rewrite too.
Confusing code is often confusing precisely because it's handling a real,
specific situation that isn't obvious from the surrounding context — this
domain's earlier debugging lesson's "assume you're missing context, not that
the code is simply wrong" applies directly here too. git blame, the original
commit message, and the pull request or issue it references are frequently the
fastest way to recover that missing context, often revealing that a strange
line is a deliberate fix for a specific historical bug, not an oversight.
When no tests exist and adding proper ones isn't immediately practical, other safety nets still help: running the system manually and recording its exact current output for a range of real inputs, feature-flagging a change so it can be disabled instantly if something goes wrong, or deploying to a small percentage of traffic first. None of these are a full substitute for real tests, but each reduces the actual risk of an unfamiliar-code change relative to just shipping it and hoping.
Once you've done the work of understanding a confusing piece of legacy code, that understanding is valuable far beyond your own current task — leaving a comment explaining the non-obvious "why," updating a README, or simply writing a clear commit message means the next person (very possibly you, months later) doesn't have to redo the same archaeology from scratch.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Under Michael Feathers' definition used in this lesson, what makes code 'legacy'?
2. Why is 'rewrite it from scratch' often the wrong instinct when facing messy, working legacy code?
3. What is a 'seam,' in the context of working with legacy code?
Software Engineering Fundamentals