The crux of the problem with changing nasty code is that there may be insufficient or no code coverage around the code you want to change. The technique that I describe below will help you gain the confidence you need to renovate the code to a squeaky clean state.
Step 1. Start Clean
Its important to start with a clean state. If you have done some exploratory spiking, make sure that you revert those changes or diff them off to a file. If there are tests around the method run them all and make sure they are green. If there are no tests, create one that will cover the success path of the function. This should be at a fairly high level and should have as little mocks as possible. We really want to understand the behavior here, not necessarily the interactions. If you are finding this test difficult to write, get a copy of Michael Feather's Working Effectively with Legacy Code. Its a great resource.
Step 2. Probe for Weakness
It may seem counter-intuitive, but the next step is to break the code in every way imaginable. Delete if statements, swap parameters used to call other methods, negate boolean expressions, anything you can think of. Each time you make a change, re-run the tests and make sure that at least one fails. If there are no failures, create a failing test. After each change has caused a failing test, revert your changes to the method and ensure that the tests once again pass. Repeat this until you feel comfortable that you have good coverage. This is usually a good point to check in.
Step 3. Make Room for the new feature
Now that we have sufficient coverage around the code, we can now begin changing the method to make room for our new feature. The key here is not to introduce any new functionality. We will lean on the tests that we just wrote to ensure that we have not broken anything. Once we have "extract method"-ed and "extract class"-ed enough to fit our new feature in, its time to check-in again.
Step 4. TDD the new feature
We have now gone to all the trouble of building a solid test suite around our code, it would be a shame to screw it up now by introducing untested changes. Therefore, we will TDD the new feature in by writing new tests or changing existing tests that fail and add new production code to make them pass. This may span multiple check ins depending on the complexity of the feature.
Step 5. Applaud yourself for being a mature developer and good teammate
Nobody likes cowboys and cowgirls in their code base. You should feel proud that you have just completed a feature like a professional. It may of taken more time that it would have to "just put it in", but now you are less likely to have an expensive defect in production. Give yourself a pat on the back.