Tests prevent regressions. There are other tools which would prevent regressions, but unfortunately they are either less effective (example: code reviews), or extremely expensive (example: formal proofs), which leaves us with tests as the best option.
So you find yourself in front of a codebase which should be tested, but is not tested yet, or not enough, and you have to make a change. The solution to prevent regressions is to progressively increase the test coverage.
When you touch a small part of the codebase, check if it's tested. If you are confident enough that most if not all the logic is tested, that's great. If not, start by adding tests. The great thing is while you do it, you will:
- Understand better the part that you wanted to modify.
- Have a better view of how well this part of the code matches the business requirements.
- Find a bunch of errors in the logic of the code, such as the branches which can never be reached.
- Get a clear picture of the change you were expected to do.
- Imagine some possible refactoring to do.
The elements in bold would reduce the risk of causing regressions by a change. Most regressions come from changes where developers misunderstood the code they were changing, or misunderstood the change itself.
Once you tested it:
- Write the test which will check for the change you're about to do.
- Do your change.
- Re-run the tests.
- Refactor.
- Re-run the tests.
- Release.
If you're not under time pressure:
- Make additional effort to refactor more, if needed. Sometimes, there are opportunities to simplify the code tremendously, eventually replacing dozens of lines of code by a call to a method, or removing a duplication, or making a challenging algorithm easy enough for a junior programmer to understand. Since the code is tested now, you can refactor aggressively, without the risk of creating regressions while doing so.
- If during testing, you noticed discrepancies between code and business requirements, go see the product owner.
- If you still have free time, test code that surrounds the piece you already tested, especially if it allows to expand the refactoring further on.
Make sure you also read I've inherited 200K lines of spaghetti code — what now?