Living Dangerously: Refactoring without a Safety Net
It’s usually a good idea to have unit tests in place before refactoring some code.
I’m going to go against the grain here today though and tell you that it is not always required.
Many times code that should be refactored doesn’t get refactored due to the myth that you must always have unit tests in place before refactoring.
In many cases the same code stays unimproved over many revisions because the effort of creating the unit tests needed to refactor it is too high.
I think this is a shame because it is not always necessary to have unit tests in place before refactoring.
Forgoing the safety net
If you go to the circus, you will notice that some acts always have a safety net below because the stunt is so dangerous that there is always a chance of failure.
You’ll also notice that some acts don’t have a safety net because even though there is risk of danger, it is extremely small, because of the training of the performers.
Today I’m going to talk about some of the instances where you don’t necessarily need to have a safety net in place before doing the refactor.
This is an easy one that should be fairly obvious. If you use a modern IDE like Visual Studio, Eclipse, or IntelliJ, you will no doubt have seen what I call “right-click refactor” options.
Any of these automatic refactors are pretty much safe to do anytime without any worry of changing functionality. These kinds of automated refactors simply apply an algorithm to the code to produce the desired result and in almost all cases do not change functionality.
These refactoring tools you can trust because there is not a chance for human error.
Any time you have the option of using an automatic refactoring, do it! It just makes sense, even if you have unit tests. I am always surprised when I pair up with someone and they are manually refactoring things like “extract method” or “rename.”
Most of the time everything you want to do to some code can be found in one of the automatic refactoring menus.
Small step refactors
While not as safe as automatic refactors, if you have a refactor that is a very small step, there is a much higher chance your brain can understand it and prevent any side effects.
A good example of this would be my post on refactoring the removal of conditions.
The general idea is that if you can make very simple small steps that are so trivial that there is almost no chance of mistake, then you can end up making a big refactor as the net effect of those little changes.
This one is a judgment call. It is up to you to decide if what you are doing is a small step or not.
I do find that if I want to do a refactor that isn’t a small step refactor, I can usually break it down into a series of small steps that I can feel pretty confident in. (Most of the time these will be automated refactors anyway.)
Turning methods into classes
I hate huge classes. Many times everyone is afraid to take stuff out of a huge class because it is likely to break and it would take years to write unit tests for that class.
One simple step, which greatly improves the architecture and lets you eventually create unit tests, is to take a big ol’ chunk of that class, move it to a new class, and keep all the logic in there exactly how it is.
It’s not always totally clean, you might have to pass in some dependencies to the new method or new class constructor, but if you can do it, it can be an easy and safe refactor that will allow you to write unit tests for the new class.
Obviously this one is slightly more dangerous than the other two I have mentioned before, but it also is one that has a huge “bang for your buck.”
Unit tests, or test code themselves
Another obvious one. Unless you are going to write meta-unit tests, you are going to have to live a little dangerously on this one. You really have no choice.
I think everyone will agree that refactoring unit tests is important though. So, how come no one is afraid to refactor unit tests?
I only include this example to make the point that you shouldn’t be so scared to refactor code without unit tests. You probably do it pretty frequently with your unit tests.
I’m not advocating recklessness here
I know some of you are freaking out right now.
Be assured, my message is not to haphazardly refactor code without unit tests. My message is simply to use temperance when considering a refactor.
Don’t forgo a refactor just because you are following a hard and fast rule that you need unit tests first.
Instead, I am suggesting that some refactorings are so trivial and safe that if it comes between the choice of leaving the code as it is because unit testing will take too long, or to refactor code without a safety net, don’t be a… umm… pu… wimp. Use your brain!
Things that will bite you hard
There are a few things to watch out for, even with the automatic refactoring. Even those can fail and cause all kinds of problems for you.
Most of these issues won’t exist in your code base unless you are doing some crazy funky stuff.
- If you’re using dynamic in C#, or some kind of PInvoke, unsafe (pointer manipulation) or COM interop, all bets are off on things like rename.
- Reflection. Watch out for this one. This can really kick you in the gonads. If you are using reflection, changing a method name or a type could cause a failure that is only going to be seen at runtime.
- Code generation. Watch out for this one also. If generated code is depending on a particular implementation of some functionality in your system, refactoring tools won’t have any idea.
- External published interfaces. This goes without saying, but it is so important that I will mention it here. Watch out for other people using your published APIs. Whether you have unit tests or not, refactoring published APIs can cause you a whole bunch of nightmares.
This list isn’t to scare you off from refactoring, but if you know any of the things in this list are in your code base, check before you do the refactor. Make sure that the code you are refactoring won’t be affected by these kinds of things.