We've all seen code bases that look like they might have been written as an ode to Fear and Loathing in Las Vegas. There's a good chance that we have even perpetrated such code. But now it's in production and the support is making you wonder why you didn't take that nice accounting job out of school. You could put tests on the code but it's such a mess that you should probably just throw it out and start fresh, if only you had the time and the Business wasn't howling about the way the software is causing them to bleed cash. Fear not, there are strategies that can help you out.
The single biggest risk is all the places where your code calls out to other systems. In a micro-services architecture, each of your major components is probably calling lots of other major components. Even in a simple CRUD app your code has to be calling a backing data store. There's a good chance you're also talking to an email service. At bare minimum, your code is likely to be calling system functions.
Any place where there is a function call outside of the class you are testing, there is an opportunity for failure. Some are bigger than others. If the call involves a network connection it's at particularly high risk for failure and you should test those first. Following that I'd check system calls, because those represent the biggest risks--if a system call fails, your software is most likely in trouble. After system calls, looks for calls to other parts of your system.
Every place where there's an opportunity for failure, you should write a test. The test should test not only what can go right, but also what can go wrong. There are simple cases, such as no data was returned. There's error responses, such as what happens to your http requests when the network suffers a partition. And there's the subtle cases, where the data returned isn't the data you were looking for: a different object is returned, or the right object with a member that indicates an error, or even the right object with different levels of available information (thanks PayPal).
Then, and this is key, you have to make the tests pass. Show no mercy: this is the code that's making your life misery by drowning you in production support.
Will this solve all of your problems with this legacy code? It probably won't, but it will eliminate a lot of the bigger pain points. As developers we tend to be optimists, and don't write enough code to cope with the situations where things go wrong. There's still plenty of room for errors in your program logic, or for the business to change and the software to lag behind.
One of the nice benefits of these changes is that you can now refactor your code much more safely. If it looks less like a trip through bat country, and more like it was written by a sane and sober person, you'll have an easier time maintaining the code. Conversely, you should also have less production support dragging you down.