In just about any software project, you’ll come to a point when you need to refactor code. Sometimes, this can be small scale—maybe it’s just reworking a function. Other times, it’s more large-scale, affecting large pieces of the codebase.
It’s really rewarding to get a chunk of code refactored, but it comes at a price; time spent refactoring is time you aren’t developing features. Of course, the hope is that by spending the time to refactor today, you will save yourself time in the long run by having a project that is easier to use.
Here are a few practices that I’ve found helpful in deciding when to refactor.
1. Don’t Wait Too Long
It’s easy to get caught up in creating features and neglect code cleanup. However, without taking a step back and spending some time on general code maintenance, you are going to accumulate technical debt. A great example is when you determine that multiple components of your app are reimplementing the same functionality. Following the DRY principle, you will want to abstract this, and preferably before you’ve repeated yourself too many times.
2. Don’t Refactor Prematurely
On the other end of the spectrum is refactoring before it’s needed. I find that sometimes when I encounter new code, my first thought is, “Well that’s not how I would have done that.” Maybe this means the code should be refactored, but it could also be that I haven’t thought it through fully yet. Following the principle of YAGNI, I like to hold off on developing abstractions until I’ve had to repeat myself once or twice.
3. Document Areas of Your Codebase You Want To Update
On my current project, we have worked to document areas of the codebase that are minor annoyances–things we don’t need to refactor today, but should take care of sometime. By keeping a list of such issues, it’s very easy to spend an hour here or there going through a few of the bullet points.
4. Make Small Refactorings as Part of Other Tasks
As part of the development cycle, you’ll likely find functions in need of cleanup, or patterns that are repeated and should be abstracted fairly often. When I come to these points, I’ve found it helps to determine the complexity of the potential refactor.
If it’s moderately complex and I don’t want to be distracted from the current feature, I’ll throw it on the list. If it’s a quick fix, I’ll get to it as part of the feature I’m working on.
When I’m working on implementing a new and complex piece of functionality, I find it really helpful to refactor as soon as I get things working and tests are green. While working on a feature, I tend to envision more elegant solutions as I work through the problem, but I like to hold off until I’m at a good stopping point.
5. Make Sure You Have Good Test Coverage Before Refactoring
Because refactoring is all about changing the internals while preserving the same behavior, you want to be sure you didn’t accidentally change the program’s behavior. If you have good testing around the code you will be changing, you can make changes without fear of such mistakes. Because of this, it’s a good plan to review the tests in place, and add to them if there are any gaps. Integration and system tests are often very valuable here.
What are your guidelines for refactoring? Share your thoughts in the comments.