I was reminded yesterday of a very important step I had been forgetting when working with legacy code. The first step. Refactor.
Working with legacy code can be challenging. Especially code that was written by someone who didn't know what they were doing and then modified 10 times by someone who didn't care what they were doing. (This is perhaps 90% of legacy code.)
I was having the tendency to jump right in and start implementing my clean feature in my own class that I would integrate into the legacy code, and move the relevant logic into my own class.
The good way
Let me see if I can summarize what my steps have looked like:
- Create failing unit test for new functionality I am implementing.
- Create new class which only has the logic for that functionality, but that I know overlaps some of the legacy code.
- Repeat until my functionality is working.
- Integrate the use of my class into the legacy code.
- Start moving parts of the legacy code that share the responsibility of my class into my class.
- Refactor the remaining legacy code.
The better way
I don't think these steps are that bad. But there is one problem, which is easily solved by adding the step of “Refactor the legacy code” at the beginning. The problem is that of clearly knowing the responsibilities of the legacy code. Most of the time in this situation the legacy code has multiple responsibilities. When you are done implementing the new functionality and cleaning up the code, you should end up with several classes in single responsibility (SRP).
The problem is that poorly written legacy code tends to hide all of the things it is doing. Refactoring the code first allows you to be able to understand better what responsibilities may be hiding in the code. It also allows you to better see the true structure of the logic, which helps to clearly identify the class you want to pull out to put your new logic in.
So, a better set of steps for adding functionality to legacy code is:
- Refactor legacy code to be as clean as possible.
- Create failing unit test for new functionality I am implementing.
- Create new class which only has the logic for that functionality, but that I know overlaps some of the legacy code.
- Repeat until my functionality is working.
- Integrate the use of my class into the legacy code.
- Start moving parts of the legacy code that share the responsibility of my class into my class.
- Refactor the remaining legacy code.