We’re all looking at ways to clean up our code, reduce complexity and improve functionality. Refactoring provides a way forward.
This guide will cover the following topics:
According to Martin Fowler, author of two books on refactoring,
”Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure. It is a disciplined way to clean up code that minimizes the chances of introducing bugs. In essence when you refactor you are improving the design of the code after it has been written.”
Source code refactoring offers numerous advantages. It turns messy, incorrect and/or repetitive code into clean code. It addresses the standardization problems which can occur when multiple developers contribute their own code. Refactoring provides greater readability and improves the maintainability of the source code as well as the overall structure and functionality. Refactoring can make code easier to expand upon and add new features. Removing unnecessary parts such as duplications can also result in code that uses less memory and performs faster.
For example, in 2014, Kickstarter engineers were faced with the challenge of exponential growth in the number of users which caused degradation of query performance. In response, they refactored a MySQL query to Redis and cut over 100ms off typical load times, resulting in a reduced variance of load time and an overall faster site.
Simply put, refactoring is a way of removing or decreasing technical debt.
Refactoring is essential to maintain long-term code quality, security, and performance. Without factoring regularly, developers are left with a mammoth amount of technical debt. This debt grows as more opportunities for code refactoring are missed and as a result, new development becomes difficult especially that built on legacy code.
💡 Pro tip: try out the Stepsize VSCode or JetBrains extensions that will help you track and prioritise technical debt directly from your editor.
Using metrics allows you to prioritize the main fixing action you really need to make to your code. It stops you from trying to do everything all at once and focuses on the most essential tasks first.
Further, you need metrics to qualify the efficacy of source code refactoring - it’s not just about changing inefficient code, but changing inefficient code to add value. To gain real value you need tests, both unit (such as the number of failed unit tests) and functional. Other metrics could include finding less bugs and reduced cyclomatic complexity - refactoring should aim to decrease complexity. Methods or functions with high complexity (such as those over 350 lines) are good refactoring targets.
It’s also worth considering how refactoring fits with broader team objectives or milestones in regard to workflow and tasks. This should include smaller code size and more understandable code.
There are numerous examples of code refactoring, but for brevity, we’re going to focus on a few:
Refactoring goes hand-in-hand with unit testing. One of the most common forms is Test-Driven Development (TDD) inherent to the Agile methodology. You write the tests before writing the code. In essence, the tests should drive the program, stating what the code should do.
Red, Green, Refactor is an example of TDD:
- Red: Write a test suite without implementation code, ensuring it fails.
- Green: Write the implementation code, just enough so the test suite passes.
- Refactor: Look for ways to optimize and improve your code.
Move a fragment of code from an existing method into a new method that is clearly named to explain its function. This technique helps reduce complexity and improve the readability of code.
If you come across an expression that is hard to understand or it is duplicated in several places throughout your code, Extract Variable refactoring can place the result of such an expression or its part into a separate variable that is less complex and easier to understand. This reduces complexity and code duplication.
Branch by Abstraction is for making a large-scale change to a software system in a gradual way that allows you to release the system regularly while the change is still in progress. This removes the complexities of refactoring code on a branch where problems can arise when you try to merge the code.
Excessively long code is hard to understand and hard to change. The Compose method refers to a range of actions that can be used to streamline methods and remove code duplication. These include Inline Method, Inline Temp, Replace Temp with Query, split temporary variables, and remove assignments to parameters.
Do you need specialist tools for refactoring? Martin Fowler says that automated tools are helpful, but not essential. He notes:
“Many languages have IDEs which automate many common refactorings. These are a really valuable part of my toolkit allowing me to carry out refactoring faster. But such tools aren't essential - I often work in programming languages without tool support, in which case I rely on taking small steps, and using frequent testing to detect mistakes.”
Many development environments automate the mechanical aspects of refactoring. Key code refactoring tools are:
To address the problems that have caused the need for refactoring requires an exploration of how your company functions. Before starting the process of refactoring, answer a few questions:
Without addressing the underlying problems that cause the need for refactoring, the problem will only proliferate.
Investing in infrastructure and maintenance may not be popular in your company.
It’s easy to argue that time spent refactoring is time spent away from new work.
But it's worth looking at the bigger benefits of refactoring and how they relate to workflow, clients, revenue, and business growth. Refactoring, done well, improves the code that needs to function well to deliver effective updates and trending features that appeal to new and returning customers. This is how a software company remains competitive even long after a successful product release.
Even better is to get buy-in for refactoring from senior management by quantifying how much time the team currently spends fixing errors or bugs due to problems in the original code. Be specific, is it one hour a day? Two hours a day? Keep a record over a week, you might be shocked to learn your team spends weeks or months every year fixing legacy code.
Is refactoring a hard sell to your team? Do people groan when it’s mentioned? The biggest markers for successful refactoring are actions that are planned, purposeful, and documented. Ron Jeffries, one of the three founders of the Extreme Programming software development methodology, likens refactoring to clearing a field:
“We take the next feature that we are asked to build, and instead of detouring around all the weeds and bushes, we take the time to clear a path through some of them.”
However, he stresses that bad code takes a long time to clean up and espouses a more thoughtful approach than simply diving in:
“We improve the code where we work, and ignore the code where we don't have to work. Odds are, we'll visit this place again.
Often within the same Sprint, we find that a subsequent feature actually uses an area we just previously cleaned. We start to get a benefit from the incremental refactoring right away. Had we waited to do it in a big batch, we'd have put in more effort, delayed any benefits until later still, and likely wasted effort on places that don't provide benefit yet.”
Product engineer and CTO Andreas Klinger is a fan of the Fix-it Friday.
“The rule of Fix-it Friday is simple: unless your current project is on fire, use Fridays to invest in little improvements. Let engineers choose what they work on. Try not to take the “fun” out of this by micromanaging. Some will try out new libraries. Some will remove bugs from the backlog. Both are fine. Try encouraging a balance of tasks.”
Whatever your approach, it needs to be considered. Ask your team what code is most hindering their efficiency.
It’s unlikely that you’ll have a large dedicated chunk of time to dedicate to refactoring at the expense of all other projects, but don’t underestimate the impact of regular, consistent, dedicated small refactors. These add up and combined, have a significant benefit.
Documentation such as standardising naming conventions can ensure everyone is on the same page. Research into reviewing refactoring by senior developers at Xerox found a lack of documentation to be one of the biggest challenges.
Documenting your refactoring work leads to a trail of time spent and provides context for future team members.
Also, document your successes - what have been the biggest wins from refactoring? Can these be factored into peer reviews?
Stepsize is an editor-first issue tracker for a healthy codebase that helps Engineers:
Start prioritising tech debt today.