In the last post I started this mini-series about what you can do if you are thrown at a legacy code base. I wrote about not rushing in, about not giving up either, but starting with small code improvements. The next step would be bigger refactorings which I will write about in this post.
There are two kinds of big refactorings. I would call one the in-place refactoring, where you take a class and change it step by step until is cleaned up and works like it should. It sometimes even works different, depending on what the goal of the refactoring was.
In-place refactoring should be guided and guarded by unit tests. You have to make sure you don’t break or change things you did not intent to break or change. Some of the steps might break a few unit tests, but you usually know beforehand that a certain refactoring step will change some behavior and therefore the tests have to be altered accordingly.
Obviously, in-place refactoring is only viable if it is possible to write those unit tests. If the class in question is heavily entangled and dependent on the whole world, you can’t write tests that execute fast and can guide you through every little refactoring step. In such a case, get rid of the hard coded dependencies first by very small and secure steps.
The second kind of big refactoring is writing a replacement for a functionality. You write a class or a little library completely from scratch, of course guided by unit tests as far as possible. Whether you do test driven development or test after the fact is a matter of taste, but I strongly recommend trying out TDD.
When the new functionality is ready, you find the places where the old functionality was used and replace it with the new version. Sometimes a functionality replacement does not need to get introduced by just throwing the old one out and the new one in, but more like a fork in the program: Both the old and new functionality coexist for a while.
Choosing between Replacement and In-Place Refactoring
Writing a replacement functionality has some advantages and disadvantages compared to in-place refactoring. While in-place refactoring can sometimes lead to a dead end where you get stuck half way with a solution that is neither fish nor fowl, flipping the switch and replacing a complete functionality can be even more risky.
I once had a huge success by replacing a legacy functionality that was tedious to maintain and sometimes prone to subtle programming errors. The replacement was a little library using a embedded domain specific language.
I did not replace the old functionality all at once, but instead started by replacing only a few simpler cases where the old functionality was called with an early version of the DSL. While there are still parts of code lingering around that use the old functionality, the main goal of maintainability is given: If one of those old calls has to change, it is easy to replace it by a call to the DSL.
While it is a little language on its own that someone has to learn first, the terseness and focus on the specific problem domain has reduced both implementation time and code size of one use of the new functionality to about 10% of a similar call to the old functionality.
Since the DSL functionality is covered by fast unit tests, it is no problem to further improve that functionality, beyond the capabilities of the old code. For example today there are hooks that allow to dynamically load and execute an external version of the DSL. I hope to one day be allowed to write in more detail about that experience, because it touches enough topics for a month or two of blog posts.
If done right, a major refactoring can give a great boost in maintainability of your code. Just be sure that you are not taking too much of a risk, therefore you should plan the steps first before jumping right in and starting to mess with the code.
Doing a bigger refactoring will need some support of your team mates. You will have to get them on your side first, because not everybody likes it if the new guy starts changing all the code one is so familiar with. In addition you will require the time to do it, maybe you even need help, so you will have to convince your boss, too. That is what my next post will be about: Getting the team into the boat.