Refactoring in testless code is hard.
It’s the perfect demonstration of the manglish "agency" of code.
It is simply not possible to change testless code and guarantee you’ve done no damage. It’s one of the most delicate operations geeks do. There are principles, yes. There are tricksy techniques, too. But mostly, there is experience & judgment. The deep trick is to turn every mistake you make into a microtest that would keep it from ever happening again.
A key insight: never debug in legacy when you’re refactoring. The code that’s there is the code. It’s been shipping. It is right.
In that way, we can gradually pull ourselves out of testless legacy and into gradually increasing confidence. So. That’s all. Be of good cheer. You get out of legacy the same way you got into it, one tiny step at a time.
Oops. One more thought. Noob TDD’ers won’t yet understand this, but the act of retrofitting microtests will reveal the correct design.
That is, a microtestable design is a good design, to some arbitrary epsilon. Making it microtestable is fixing it. As you proceed with non-net-negative endpointless steps, the code will start to scream at you about the major refactorings that are needed.