Legacy is uncertainty, not simply age

A legacy system is not defined only by the year it was created or by an old technology. More important signals are poorly documented behavior, unclear ownership, and a high cost of failure. Even a relatively new module becomes a legacy area when the team cannot confidently explain its contracts and the consequences of changes.

The goal of discovery is not to find someone to blame or to prove that the code must be rewritten. Gather enough evidence to separate confirmed facts, unknowns, and areas where a change needs additional control.

  • weakly documented behavior: actual behavior is known mostly from code or verbal explanations;
  • unclear ownership: it is unclear who will confirm a business rule or accept the risk;
  • high failure cost: a failure affects money, access, data, or a critical operational process.

What to find before any change

Check more than source code. Tests show established expectations, configuration shows runtime conditions, logs show actual runtime signals, and Git history shows frequently changed areas and previous decisions. None of these layers is sufficient by itself.

Discovery areaQuestionPossible evidence
SubsystemsWhich modules participate in the required scenario?directories, entry points, imports, service boundaries
Critical runtime flowsWhat path do input, decisions, and output take?code, logs, integration tests, configuration
Change-risk areasWhere could a small change have broad or costly impact?shared utilities, retries, flags, dates, money paths
Existing coverageWhich scenarios are already checked and at what level?unit/integration tests, coverage report, CI checks
UnknownsWhat remains unconfirmed?missing owner, conflicting docs, manual verification

Read-only discovery with evidence anchors

  1. Record the scope and the no-edit rule in a discovery note.
  2. Build a list of key subsystems and entry points.
  3. Trace critical flows through code, tests, configuration, and logs.
  4. Check Git history, ownership, and available coverage signals.
  5. Add an evidence anchor for every material claim: a path, symbol, command, log signal, or commit.
  6. Move unconfirmed questions into a separate unknowns list.
  7. Only after discovery prepare a draft RISK_MAP.md; do not change code.

Focus for a learning legacy scenario

For a research example, consider `mrr-engine`, `payments`, retry logic, configuration, and feature flags. Check `legacy/`, pause/resume, and timezone calculations separately. A reported 8% MRR discrepancy is input for investigation, not a proven root cause: connect it to specific data, code, or log evidence.

A discovery note is convenient as a table with “Subsystem”, “What is confirmed”, “Which tests exist”, “What is unclear”, and “Why it is a risk”. At the end, add sources and decisions that still need an owner or manual verification.

Discovery output

  • CODEBASE_INVENTORY.md provides a navigation map, while MODULE_INVENTORY.md details boundaries and ownership;
  • CRITICAL_FLOWS.md shows runtime flows that must not be accidentally lost;
  • the discovery note separates facts, assumptions, and unknowns;
  • a draft RISK_MAP.md turns collected signals into next decisions;
  • do not call a conclusion confirmed without an evidence anchor.

Canonical lesson source · JavaRush

Open JavaRush material