I've inherited a codebase with no meaningful tests, a large God object, roughly 20 dependencies, a hidden global dependency, and one enormous method full of nested conditionals, loops, and heavily mutated variables. I've added approval tests to capture the core behavior and some important branches, and I've renamed ambiguous variables to make their intent clearer. However, I can't feasibly cover every path yet, and extracting methods feels awkward because so much state is passed around by reference and mutated in place. There are also many existing bug tickets, so leaving the code untouched isn't really an option. What process do you use to make this kind of legacy code safer and easier to work with without attempting an enormous rewrite all at once?
3 Answers
Your approval tests were a good first move. I’d avoid trying to clean up the entire God object immediately and instead create seams around the behavior you need to change. Characterize one narrow workflow, wrap the global and external dependencies behind interfaces, then make small behavior-preserving changes. Run the tests after each small extraction and commit frequently.
Before deciding how far to refactor, compare the cost of incremental improvement with a rewrite. If the existing system has understandable foundations and mostly accumulated complexity, refactoring is usually worthwhile. If the design is fundamentally unsuitable, a replacement may be better—but it still needs carefully defined behavior, migration boundaries, and tests. Share both options, trade-offs, and your recommendation with the stakeholders, and document the decision so expectations are clear.
When state is being passed around and mutated everywhere, I usually stop extracting standalone methods and introduce a context or parameter object. Put the related mutable values into a new class, move one piece of the large method onto that class, and let the new instance methods access the state directly. You can gradually move loops and branches this way without juggling dozens of parameters. Once the behavior has a proper home, replace the corresponding part of the original God object.

That’s helpful. Moving the shared state into a dedicated class seems much more manageable than passing references through every extracted method.