Taking over an abandoned codebase for a B2B SaaS team
An undocumented service whose authors had left, brought back under test and deployable on demand without a rewrite.
Client
B2B SaaS company
Sector
B2B SaaS
Duration
PLACEHOLDER — confirm engagement length
Stack
TypeScript, Docker, GitHub Actions
Published with the client’s permission. Their name, product names and any identifying figures have been removed or generalised.
The engagement
The situation
A revenue-critical service had been built by contractors who were no longer available. There were no tests, no documentation, and deployment was a sequence of steps one remaining employee performed by hand. The team had stopped changing it, which meant they had also stopped improving the product around it.
What we did
We resisted the rewrite the team expected to be told they needed. The first milestone was getting the service building reproducibly on a clean machine and deploying from a pipeline. Only then did we add characterisation tests pinning current behaviour — bugs included — so later changes could be made with evidence rather than hope.
The outcome
The service is deployable on demand and under test. The team resumed shipping changes to it. The rewrite that had been assumed necessary was not, and the budget went to features instead.
Deployment
PLACEHOLDER
Replace with a measured before-and-after, or delete this metric.
DecisionsWhat we chose, and what it cost
Every choice traded something away.
01
No rewrite
Incremental strangling behind a stable interface, starting with the riskiest module.
Trade-off The codebase still carries legacy patterns. In exchange the service kept earning throughout, and the team was never paying for two systems at once.
02
Pin bugs, do not fix them first
Characterisation tests that assert current behaviour, including behaviour that is clearly wrong.
Trade-off The test suite initially encodes bugs. That is uncomfortable, but it is the only way to distinguish "I changed this" from "this was always broken" when you did not write the original.
03
Pipeline before refactor
Reproducible build and automated deploy as milestone one.
Trade-off No visible feature progress for the first stretch. Everything after it was faster, and the team could finally verify their own changes.
The service was not badly written. It was undocumented, untested and
unreproducible, which is a different problem with a different fix. Roughly a
fifth of the code turned out to be unreachable — a fact we could only establish
once there was enough instrumentation to prove it.
The genuine risk was not the code at all. It was that a single employee held
the deployment procedure in their head, and had been on call for it, informally,
for two years.
A reproducible Docker build, a deployment pipeline, and a characterisation test
suite covering the paths that touch money. Then we removed the dead code,
which was the first change anyone had made to the service in eight months.
What we would do differently
We under-communicated during the pipeline phase. From the outside it looked like nothing was happening for two weeks. A weekly written note explaining what had changed and why it mattered would have cost almost nothing and avoided a difficult conversation.