This is a guest post for the Computer Weekly Developer Network written by Timothy Gilboy in his capacity as co-founder & COO of Sourcery – a company known for work focused on refactoring software code, with specific capabilities aligned to the Python software language and framework.
Gilboy argues that in most software engineering organisations, it’s accepted practice that you need to periodically refactor your code to keep it from being bogged down by legacy code, or to make future development easier.
However, it is often thought of as an annoying task to do, with little consideration given as to how critical of a piece of the development process it is. He says that over time, the decisions we make as developers lead to a build up in complexity, technical debt and issues within projects that will slow down future development.
This debt is significant enough that a developer on a typical team spends north of 30% of their time dealing with issues solely around technical debt. Clearly, it is something we need to focus on, but what is the best approach?
Gilboy writes as follows…
Refactoring is often viewed as a separate project, or process, from standard development.
In these cases there are typically three main reasons or times when you will want to refactor:
Before a new project. It’s a great opportunity to refactor before you add in any new updates to an existing feature, or begin a new project. You have an understanding of how you are going to be looking to extend the existing code and can easily identify opportunities to refactor that will make the upcoming project easier and more efficient.
After shipping or new feature.
The first version of a project that gets completed tends to involve a few shortcuts or design decisions that were made in order to quickly get it out. Plus, you have likely learned more about the optimal structure and design of the project as you’ve progressed through development and there are decisions you made early on that you may have done differently if you were able to start over. All of this context is fresh in your mind when you have just completed the project, making it a prime opportunity to go back in, refactor and clean up the code so it will be easier to work with in the future.
A critical point of slowdown.
It can be hard to measure, but you know it when you feel it. If left unmaintained and un-refactored, all codebases hit a critical mass of technical debt which will critically slow you down. Everything starts taking much longer than it should, bugs are more common problems, and team morale starts to slip. Ideally, you’ll start to refactor before you hit this point, but if you do not then this is a key time to refactor.
Regardless of which of these situations you are in when you are refactoring, the general approaches and requirements are similar:
Robust test coverage will make your life significantly easier when you are looking to significantly refactor. By having tests in place you can increase your confidence that any of the changes you are making will not impact the functionality of your code in any significant way. This allows you to be more aggressive in the changes you are making and increase the impact of your refactoring efforts.
Look for small changes you can make in your code to drive improvement and keep building on these improvements. This makes it easier to improve things without having a significant risk that you are altering the functionality or introducing new bugs.
While not strictly necessary, pairing can be a major help when undertaking significant refactoring projects – especially for complex codebases. Having a partner allows you to tap into their knowledge and you can work together to think of better options for how to structure things.
In the traditional process, refactoring is treated as an isolated exercise – hopefully done frequently, but more often than not delayed until the true pain points of poor code quality are felt. Even in the best-case scenarios, this means that you are introducing inefficiency and technical debt into your projects in the time between refactoring efforts. All of this creates significant cumulative slowdowns that can be avoided if you reposition code quality improvement/maintenance to be a continuous effort.
Throughout the development process, you should be on the lookout for decisions that are reducing the quality of your code and fix them as quickly as possible. This does not fully replace the need for periodic larger refactoring efforts (some complexity, quality issues, and technical debt will creep into things regardless), but instead reduces the slowdowns and impacts on your team in between refactoring projects.
To effectively measure and improve code quality continually you can use:
- Manual code reviews
It may seem obvious, but having a robust culture of manual code reviews helps to ensure that poor design decisions are not introduced into the codebase in the first place. Plus, good code reviews are great opportunities for knowledge sharing and helping the entire team improve as developers.
- Static analysis and code quality measurement tools
It is easier to manage code quality if you have a clear cut way to measure it. By using static analysis tools you can flag frequent issues and code quality measurement tools give you benchmarks for your code on several metrics. Measuring how these metrics shift over time allows you to flag what changes you have made are significantly making your code better or worse.
- Automated refactoring/code review tools
More tools are emerging that continually review your code as you are creating it, and give suggestions for how it can be improved or refactored. These can help identify code quality risks and instantly mitigate them.
Refactoring plays a critical role in maintaining a codebase that allows for rapid development, but it’s important to not to view this, and code quality, as an isolated process. Technical debt and slowdowns can accumulate quickly, so it is critical to take a proactive approach throughout the entire development process, rather than a reactive one that is implemented too late.