Software Engineering DISCUSSION

How do I start changing a large legacy codebase that has no tests without breaking it?

Started by ayesha legacy codecharacterisation testsrefactoringseamstechnical debt
5 replies 248 views 6 participants
Latest activity · 30 Sep 2026

How do I start changing a large legacy codebase that has no tests without breaking it?

ayesha Software Engineering Forum
#1

I have taken over about 150,000 lines of code for a test-rig control and data-logging application. There are functions of several hundred lines, global variables everywhere, no automated tests and almost no documentation. I have to fix bugs and add features, and each change I make seems to break something unrelated.

A rewrite is tempting but I doubt I would be given the time. What is a sound technical approach to getting code like this under control step by step?

Community replies 5

Re: How do I start changing a large legacy codebase that has no tests without breaking it?

#2

Start with the basics that make every later step safe. Get everything into version control, including build scripts and configuration. Make the build reproducible: one command on a clean machine produces the same program that is running on the rig. Make sure you can put the previous version back quickly.

Resist the rewrite. The ugly code contains years of fixes for situations nobody wrote down, and a rewrite has to rediscover each one while you still maintain the old system. Replacing it piece by piece behind stable interfaces gets you to the same place with a working program at every step.

Re: How do I start changing a large legacy codebase that has no tests without breaking it?

#3

Before changing an area, pin down what it does now with characterisation tests. These do not assert what the code should do; they record what it actually does. Feed the module recorded inputs, capture its outputs (result files, messages sent, values logged) and store them. After each change, run the same inputs and compare.

For a data-logging path that could be fifty recorded input files whose outputs are compared byte for byte, run by one script. It needs no understanding of the internals, takes a day or two to set up, and immediately tells you when a change altered behaviour you did not intend to touch.

Re: How do I start changing a large legacy codebase that has no tests without breaking it?

#4

To get finer-grained tests you need seams, places where you can substitute a dependency without editing the logic. In rig software the usual obstacles are hardware I/O, the clock and the globals. Wrap each behind a small interface or a set of function pointers so that a test can supply a fake instrument and a fake time.

Get there with small mechanical refactorings, such as extract function, pass a global in as a parameter, or move a block behind an interface, preferably done by the IDE's refactoring tools and committed one at a time. For new logic, write it as a new, tested function and call it from the old code, instead of threading more lines into a 600-line routine.

Re: How do I start changing a large legacy codebase that has no tests without breaking it?

#5

Do not try to clean everything. Most of a legacy codebase is stable and rarely touched, and refactoring it is risk with no return. Use the version-control history and the bug tracker to find the hotspots: files that change often and keep producing defects. Those are where tests and restructuring pay back.

For the rest, apply a simple rule: leave each piece of code you have to touch slightly better than you found it, with a test around the part you changed and clearer names. Over a year of normal feature work the frequently used paths become covered and readable, and the dead corners stay as they are, harmlessly.

Re: How do I start changing a large legacy codebase that has no tests without breaking it?

#6

Add automated safety nets that cost little. Turn compiler warnings up and run a static analyser; record the existing findings as a baseline and insist only that new code adds none. Put assertions and logging at the boundaries you are learning about, so the running system tells you which paths and value ranges actually occur. Run the characterisation suite on every commit with a continuous integration job.

Finally, write down what you learn as you go: a one-page sketch of the modules and data flow, and a glossary of the domain terms and signal names. It is the documentation you wished you had been given, and it takes minutes while the knowledge is fresh.

TEP COMMUNITY