One of the most common things I hear about refactoring is that there isn’t enough time for it. I hear that there is important refactoring to the code and architecture that will take days or weeks and there’s no time to fit that in because of tight timetables.
Often times teams write off refactoring as something other teams with more time get to do, but not them. The truth is all teams have tight timetables for delivering their software and the key to refactoring is not more relaxed deadlines (deadlines do need to be realistic, but that’s another topic).
A quote attributed to Aristotle tell us: We are what we repeatedly do. Excellence, then, is not an act but a habit.
The same is true for great coding and, by inference, refactoring. To effectively make refactoring part of our development process, it must not be a large effort that we spend days or weeks on, but rather, a habit that we practice continuously while writing code. So, if we want to make refactoring a habit, that raises an important question.
When Should We Refactor?
Always! When we refactor in small increments as we develop code, we’re able to spend a few minutes at a time keeping our code at a high level of quality. Let’s look at the TDD (Test-Driven Development) cycle to see how it integrates refactoring.
In test-driven development, there’s a spot after every time you’ve successfully written a segment of code to evaluate the code and refactor it. In practice, most times you look at your code and you’re happy with it or there’s a small refactor, like extracting a method, which takes a minute or two of your time. On rare occasions, a significant refactoring becomes apparent and takes up 20 – 30 minutes of your time. After each code refactor, you run your tests again to make sure you didn’t break anything.
Another often forgotten trick is to use the automated tools at your disposal. Most IDEs have refactoring facilities built in nowadays. Learning how to use them correctly will save a lot of time and reduce the risk of errors. Knowing what change to make is still essential.
Does this save you any time in refactoring compared to doing a large effort after several months? No, I don’t think it does. In fact, I think you’ll spend more time refactoring. That’s because when you spend a few minutes at a time, it’s easy to absorb that time into the development process. It’s just part of what it takes to get the work done right. When you wait, you’ll probably spend less time because you won’t do all the refactoring that should be done. If you’re like many teams, you won’t do much refactoring at all. Waiting makes you feel like you have to settle for low-quality code. The team isn’t proud of the work and the business isn’t really happy with the results, but they feel like they’re stuck with it unless they pay a large ransom of technical debt to refactor it.
What About Refactoring Legacy Code?
Sure, we’ve all been in that situation where we’ve been handed a pile of code that isn’t up to our (or in fact anybody’s) standards for good code, but refactoring it would take forever. Perhaps it’s mission critical too, or written by somebody who just can’t be questioned. What do we do with it?
First, we have to acknowledge something: people always try to do their best, but circumstances can prevent them from doing a proper job. That code was probably written by people who were capable of better, but rushed through and didn’t take time to refactor because of tight deadlines. So that leads us to step 1:
Step 1: Break the Bad Habit
You can’t change what was done to the code before it got to you, but you can stop it from getting worse. As a team, agree that from here on, no code changes are made without building in time to refactor. Each time you open a file, leave it just a little cleaner than you found it.
Step 2: Set Expectations
You’ve inherited all of the problems with the code and just because it’s been given to you doesn’t mean it’ll magically get better. Make sure there’s an understanding between your team and the business stakeholders about what improvements they need and when they need them. Some things may be able to wait until they come up naturally. Others may be causing customer pain and need attention right away. These items should get worked into your backlog so they can be given appropriate priority and it’s clear what business value they are delivering.
Step 3: Isolate Your Effort
You can’t fix a huge amount of code all at once. As you need to make changes, isolate a particular behavior that you’re testing. Wrap it in tests, and refactor what you’re changing. Sometimes this is as simple as identifying a class or two where the changes occur and focusing effort there. Other times you have to put some work into isolating code.
How we work with legacy code can be very complex and is far too involved to solve in a blog post. Luckily, a very good book has been written on the subject: Working Effectively with Legacy Code by Michael C. Feathers (Prentice Hall, 2004) rightly belongs in the bookshelf of every coder.
In short, there is no silver bullet for refactoring code, but good practices will take you closer to the goal day by day. Make an effort to refactor every time you are about to close a source file and take the time to diligently chew down legacy code into smaller pieces. Always implement automated testing on the module level or higher to give you a safety net that guards against changes in the behavior of the component and learn to use the automated tools at your disposal.