Short answer
#Why the order matters
Almost everybody starts in the wrong place, and the wrong place is the code.
Reading a large unfamiliar codebase file by file feels like progress and is not. After a month you have a partial picture, it exists only in your head, nobody else can use it, and you cannot tell how much is left — which is the specific thing that makes legacy work feel endless.
The order below inverts that. Each pass produces something concrete, in a form somebody else can read, and each one narrows what the next pass has to look at. By pass five you have a bounded list of questions instead of an unbounded fear.
#The seven passes
-
What runs, and where
Before any code: which environments exist, what is deployed to each, what runs on a schedule, and what runs that is not in the repository at all.
The last one catches everybody. Cron on a server, a cloud function created in a console, a script on a laptop, a spreadsheet somebody maintains by hand. Check the cloud console and the crontabs, not just the code.
-
The inventory
Every component with a type and one readable sentence about what it is for. Services, modules, databases, queues, scheduled jobs, front ends, external systems.
This is the flattest artefact you will produce and the one that makes every subsequent conversation possible. It is also usually the moment somebody finds a component nobody knew existed.
-
The outside edges
What external services does it call? What calls it? Whose account is each dependency in? Which of the inbound interfaces have somebody relying on them?
Outbound flows nobody thinks about — a nightly file, an analytics integration, a partner feed — live here.
-
Follow the data
Where does customer data live, how does it get there, and where does it go? This is where regulatory obligation, business risk and the parts you must not break all overlap.
-
Write down what could not be established
Explicitly, with priorities and reasons. This list is the actual output of the exercise; the map is what makes it credible.
A confident picture with no gaps in it has either come from a complete analysis or has turned its gaps into silence, and from the outside those look the same.
-
Read selectively, guided by the gaps
Now open the code — specifically the parts the list points at. Reading with a question is an order of magnitude more efficient than reading to build a picture.
-
Work the list down, then start changing things
Each answered question is a piece of control recovered. Structural change after this point is ordinary engineering rather than an act of faith.
#Reading code, when you get to it
By pass six you have specific questions, which changes the technique entirely. Four things that work:
- Follow one request end to end. Pick the most commercially important path — a payment, a signup, an order — and trace it from entry point to database. One path teaches more than fifty files.
- Read the tests before the implementation, where tests exist. They describe intent, and where they disagree with the code they mark something interesting.
- Read the history of the risky file, not just its current state. Why it changed, and how often, is usually more informative than what it says now.
- Write down each answer where somebody else can find it. An answer that stays in your head has recreated the original problem with a new person in the middle.
#Finding the risky parts
Not all of a legacy system is equally dangerous, and knowing which parts are is most of the value.
Four signals, all visible from a structural picture rather than from reading:
- Concentration. One component many others depend on. Changing it is expensive; being unable to change it is worse.
- Clustered unknowns. Six open questions about one subsystem is a different situation from six spread evenly. A cluster marks the part nobody ever understood.
- An external edge with no confirmed behaviour. An outbound integration whose retry, timeout and failure handling could not be established is a live business risk rather than a technical debt item.
- Declared disagreeing with observed. Where a comment, README or design note says one thing and the code does another, somebody's mental model is already wrong.
#What to stop doing
Cleaning up as you go.
Instead Change nothing until the picture exists. Tidying code you do not understand is how a working system stops working, and the change will not be the suspect because it “did not do anything”.
Reading file by file hoping for a picture to emerge.
Instead Derive the structure first and read selectively. A month of reading produces a worse map than one analysis, in a place nobody else can reach.
Commissioning a documentation project.
Instead Documentation projects on legacy systems almost never finish, because they are unbounded and nobody can tell how much is left. A derived picture is bounded and completes.
Treating comments and READMEs as facts.
Instead Treat them as declarations worth recording. A README describing the system as it was in 2021 is not neutral — it is actively misleading.
Deciding to rewrite before you can describe it.
Instead Describe it first. The description usually changes the decision and always changes the estimate.
#How long this takes
| Pass | Effort |
|---|---|
| What runs, and where | Half a day, mostly in consoles |
| Inventory, edges, data | Hours if derived. Weeks if assembled by hand |
| Writing down the unknowns | An hour, once the passes above exist |
| Selective reading | Days to weeks, bounded by the list |
| Working the list down | Weeks to months, and it visibly shrinks |
The important property is the last column of the last row: it visibly shrinks. The reason legacy work feels hopeless is usually that nobody can tell whether it is progressing. A list that gets shorter fixes that.
#What this method does not do
What this does not do
- It does not recover why anything was built that way. Nothing can, from code alone.
- It does not tell you whether to rewrite. It gives you the inventory and the risk list, which are the inputs to that decision.
- It does not assess code quality or find vulnerabilities.
- It does not establish runtime behaviour. Nothing is executed, so load, latency and what a specific user sees are outside it.
- It cannot see what is configured only outside the repository — that surfaces as an open question rather than as a component.