· Niklas Nilsson, CEO
Is this codebase worth saving? We ran the assessment two years late.
The honest version first, because the rest is worth less without it.
Nobody made this decision
When the food-and-health platform in the previous article was woken up after up to 928 days of silence, no assessment happened. A new CEO wanted to test selling the product, the product had to run before anyone could show it, and a rebuild was never discussed. The decision that most articles about legacy code agonise over was simply not on the table.
It worked out. Roughly three person-months and the system was back. But “it worked out” is not a method, and we have an obvious interest in believing the call was right. So this is the assessment nobody ran, executed now, on the repositories as they stood that week.
Four measurements. Test coverage is not one of them — the three repositories contain 18 test files between 935, and the admin has none at all. By the usual reflex that is a rebuild, and the usual reflex is wrong.
How big is it, really
- Admin — 303 files, 17,937 lines
- Backend — 175 files, 13,771 lines
- App — 457 files, 32,037 lines
- Total — 935 files, 63,745 lines
Sixty-four thousand lines is a number a person can hold. Rebuilt honestly it is the better part of a year, and that figure belongs underneath every other argument in the room. Nothing else matters until you know what the alternative costs, and teams routinely skip this measurement and then argue about architecture for a fortnight.
Can anyone read it
Three cheap signals, all of them pointing the same way.
No god object. The largest file in each repository is 456, 750 and 780 lines. Nothing here has to be understood all at once, which is the difference between onboarding somebody in a week and in a quarter.
Change is not concentrated. The busiest 5% of files absorb 27–34% of all changes. Where a codebase has a rotten core that number runs far higher, because every feature has to be threaded through the same place.
Between 39% and 52% of files were never modified after the commit that created them. Written once, still correct. In a domain that has not shifted underneath you, that is health rather than stagnation.
The most-changed files are not source code
This is the finding worth stopping on. The four most frequently modified files in the mobile app are project.pbxproj (106 changes), package.json (100), yarn.lock (100) and build.gradle (66). The admin and the backend have the same shape: manifest and lockfile at the top, product code below.
The history states it plainly. The work did not go into the product. It went into the ground the product stands on.
Which dependencies are dead, and does it matter
On the day each repository was reopened, this is what its dependencies looked like:
- Admin, 21 April 2023 — 9 of 26 had gone over a year without a release, 6 of them over two.
- Backend, 14 April 2023 — 14 of 35, or 40%, over a year. Twelve of those over two.
That reads like a verdict. It is not, and why not is the entire point of this section.
The frameworks were alive. On the admin, React and React-DOM had shipped a release that same day. On the backend, Koa two days earlier, mongoose eight, axios nine, dotenv seven. The load-bearing pieces were all in active maintenance.
The fossils were leaves — npm-run-all, pluralize, semantic-ui-sass, humps, shuffle-seed. A pluraliser untouched since 2019 is not a risk; pluralisation has not changed.
So far, so tidy: stale leaves are noise, a stale core is a rebuild. Except that rule is too simple, and the backend is where it breaks.
The part that looked worst
Three of the backend's fossils were the authentication stack:
passport-strategy— last published 2013passport-local— 2014passport-http-jwt-bearer— 2015
Ten-year-old packages, sitting in the path every request takes. That is not a pluraliser. If you are going to be frightened by one number in this article, be frightened by that one — it is exactly where abandoned code is least welcome.
And it was a non-event. Those three are still in production today at the same versions, untouched through the restart and everything since. passport-strategy has not changed since 2013 because it is a small stable interface with nothing left to change.
So age is not the risk. The risk is attack surface multiplied by abandonment. A tiny interface package can sit for a decade in safety. A deprecated SDK with a network surface — aws-sdk v2 is still in there, and is formally deprecated — is a different conversation entirely, and it is younger.
Is the domain still true
The only one of the four that cannot be measured, and the one that actually decided this.
The rules encoded in the system still described what the client does. Organisations, roles, rotations, assessments — the model held, so waking the system meant getting it to run rather than rethinking what it was for.
That is the difference between a bill and a write-off. Technical debt is payable. A wrong domain is not. A tidy, well-tested codebase that models a business nobody runs any more is worth nothing, and a scruffy one that gets the domain right is worth a great deal. No tool will tell you which you have. Read the core model out loud to somebody who runs the business and watch whether they recognise it.
We worried about the wrong thing
Going in, the concern was the backend and its dependencies — reasonably, given 40% of them were stale and the auth stack dated to 2013. The admin was small enough not to worry about. The app's risk was thought to be second-hand, whatever a backend rebuild might cascade into it.
The backend turned out fine. The auth fossils never broke. And the largest bill of the three was the app, on its own account: 346 source files, 37 native build files, and four days of commits titled “Starts on ios” and “Starts on both android and ios” before a single feature was touched.
The measurable thing and the risky thing were not the same thing, and no amount of staring at package.json would have said so. What separates them is not what a dependency is but what it touches — and on mobile, what touches you is two operating system vendors moving on their own schedule.
What it actually cost
Roughly three person-months, spread across several people, and no unpleasant surprises. It went about as expected.
That is a duller ending than a war story, and it is the result. When the four measurements above come out the way they did here — small, legible, live frameworks, intact domain — a dormant system is a bill, not a rebuild, and the bill is payable.
Run it before somebody asks you to
None of these needs us, and you want the numbers before you are talking to anyone who charges by the month.
- Size. Count files and lines. Then price the rebuild, and let that number sit underneath the rest of the conversation.
- Legibility. Find the largest file. Check how concentrated the changes are. A small handful of files taking most of the churn is the thing that will make every estimate wrong.
- Dependency health. Look up the last release date of each dependency — then sort them by what they touch, not by how old they are. Auth, payments and anything parsing untrusted input first. Presentation last.
- Domain truth. Nobody can measure this for you, and it outranks the other three.
We ran ours two years after the decision it should have informed. The answer came back “save”, which is the answer we had already acted on — but we found that out afterwards, and that is the part worth not repeating.