Legacy codebases are like collapsible tower games. One might try their luck to add or draw a block, but never know which exact step will collapse the structure.
If the cost of uncertainty is too high, the alternatives are to rewrite or refactor. Rewrites take massive amounts of time and if not started correctly they’ll take you back to square one, with an unsustainable haphazard structure.
Refactors are viewed as the better practice for most cases, but any specialist will tell you remodels cannot be done without a thorough testing suite in place. Therefore you are best served by investing time in adding tests to your codebase. In addition to supporting future refactors, the immediate benefit is delivering a few new features with a little more speed.
One notorious effect of legacy code is that new code causes errors to appear in a seemingly unrelated place. To keep with the game analogy, you’ll be playing whack-a-mole. Because tests produce instant feedback when something breaks, bugs are less likely to reach production.
You can’t add tests for everything in one shot, but you can start with the most important features. Stakeholders can help pinpoint what those are. For QA, it might be that thing that breaks almost every release. For marketing, it might be that part which they demo to every potential customer. BDD and Cucumber can be helpful here because they help get everyone on the same page and produce clear specs to implement.
Precise requirements and automated tests can become a protective casing for legacy codebase and help it live longer. Additional measures might or might not be still needed, but the collapsible structure game can come to an end.