Refactoring without tests is hard and really, really scary but sometimes you've inherited (or perhaps written yourself) a big ball of untestable mud and the only way out is to refactor and make it testable. One of our engineers found himself in that situation today so we sat down together to try and make sense of it.
I tried to follow a few simple rules:
- Go slow. No big sweeping changes. Keep the way it does things the same and just break the code down to make it more readable and create multiple small balls of mud instead of one big one.
- Use the automated refactorings in the IDE unless you absolutely have to make manual changes. We discovered a number of bugs in Eclipse this way where it didn't detect identical code – who knows why not. In once case I managed to convince it the code was identical by repeatedly trying the extract method until it picked up the duplicate block but in a second case I had to revert to manually replacing the duplicate code.
- Use the simplest refactoring that could possibly make it testable. Mostly this meant applying extract method with a smattering of extract local variable to pull out the differing pieces so duplicate code could be merged. In hind-sight I may have been better off leaving the duplicate code in and writing tests for it separately, it would then have been easier and safer to remove the duplication.
All in all I think it went really well, slow going but effective and we now have half as much code and it's much easier to test. There's still a lot more work needed to stop that code from smelling but it's a start and that's enough for one day. You can't spend all your time refactoring legacy code, new features have to be added – just make sure that you're constantly improving and eventually you'll get there.