The Complete Engineer's Guide to Code Refactoring
Welcome to The Complete Engineer's Guide to Code Refactoring.
This guide is dedicated to the art and science of refactoring a complex codebase. We'll look at why it's essential for modern engineering teams, and how best to go about it.
Perhaps even more importantly, we’ll explore how the efficiency or inefficiency of your wider software development lifecycle impacts refactoring work. We’ll help you understand how to spot underlying process problems or underlying technical debt. Then we’ll look at how to tackle them.
Gartner estimates that organisations with a proper strategy for tech debt and refactoring will ultimately ship 50% faster.
In this guide, we’ll explore…
- What is refactoring?
- Why should your team refactor code?
- How should you approach refactoring?
- What types of refactoring are there?
- What are the code refactoring best practices?
What is refactoring?
Refactoring is the process of removing or decreasing technical debt by improving your codebase, without creating new functionality.
The process of refactoring involves rewriting dirty code to turn it into clean code.
Clean code is code that is easy to read and understand, and that is well-structured and efficient. It’s written with maintainability in mind and is significantly less likely to cause bugs to be introduced in the future.
Why should you refactor?
Refactoring needs to be a continuous habit built into the everyday engineering process of any modern engineering team.
Refactoring is important because it lowers the cost and speeds up the software development process in the medium and long term.
That’s because it makes the codebase more:
When development teams fail to refactor regularly, the tech debt grows. New development becomes increasingly difficult, costly and slow.
The foundation of good refactoring (that most teams miss)
Most teams, even in modern businesses, haven’t mastered the process that sits underneath high-impact refactoring.
Accruing technical debt enables teams to ship faster. But tech debt needs to be accrued strategically, and managed properly.
The foundation of good tech debt management is a robust system of tracking, planning, prioritising and fixing technical debt issues.
It looks like this:
- Track high-quality issues, linked to code. Context is crucial. If your issue tracker doesn’t support linking issues to code, find a tool that allows this.
- Prioritise high-impact issues. Because issues have context, they can be themed and prioritised.
- Refactor the codebase. Protect long-term codebase health and ship better code in a sustainable way.
How to refactor while creating new features
Red, Green Refactor is a best-practice model for refactoring while creating new features. It is an example of Test-Driven Development (TDD). It’s inherent to the Agile methodology.
Red: Write a test suite that describes the desired behaviour of your code. Ensure the tests fail.
Green: Write the implementation code so that the test suite passes.
Refactor: Optimise your code.
Types of refactoring
There are a range of approaches and techniques which can be applied to refactor code effectively. We’re going to focus on four categories of refactoring which you can explore. There are dozens upon dozens of refactoring techniques. If you’re looking for specific refactoring techniques, check out Martin Fowler’s resource or these refactoring techniques.
1. Composing methods
What is it? Streamlining methods in order to minimise risks of introducing bugs and inefficiencies.
You need this when… You have excessively long functions – the root of all evil.
Why do it? Reduce the development resources needed to work on a part of the software system over the long term.
An example: Method extract – Move a code fragment from an existing method into its own new method.
2. Organising data
What is it? Untangling data with a range of techniques
You need this when… Your data structures mean your data and classes can’t be worked with easily
Why do it? To make things like classes more portable and reusable
An example: You’ve got loads of identical instances of the same class. Replace it with a single reference object.
3. Simplifying conditional expressions
What is it? Using techniques to stop conditionals getting complicated
You need this when… You’re using conditionals, and they’re getting unruly
Why do it? To make conditionals less risky for introducing bugs
An example: You’ve got multiple conditionals with the same result. Consolidate them.
4. Simplifying method calls
What is it? Using techniques to make method calls easier to understand
You need this when… You’re calling a method
Why do it? It simplifies the interfaces for interaction between classes and reduces bug risk
An example: A class you’ve created has features you’re only using in certain cases. Create a subclass for these.
Refactoring best practices for modern engineering teams
Track high-quality issues to refactor impactfully
Often, engineering teams don’t refactor simply because it’s not obvious what could be refactored, or what it could improve.
Knowing what to refactor starts with tracking issues. Accruing tech debt is inevitable, so the first move for the engineering manager should be to find ways to remove the friction engineers face when tracking issues.
Issues can only be prioritised if they are loaded with context. The main reason prioritising refactoring is ineffective is that issues are vague and not linked to code. Link issues to code in your IDE with a tool like Stepsize in VSCode or JetBrains if you’re working in an engineering team. Or if you are working on a solo project, track your TODOs with Todo Tree.
Encourage issue-tracking directly from your code editor. Link issues directly to code. These changes make it possible to plan and prioritise refactoring work properly.
Refactor first, new features second
Projects cost 55% more when the foundations aren’t built properly. Spending a little longer to refactor before adding features will save both time and money in the long run. Test regularly.
Track technical debt consistently
Technical debt is an inevitable part of the software development cycle. When you need to take a shortcut, ensure the tech debt is adequately documented and that the issue you create is linked directly to the code.
Plan and prioritise refactoring work properly
Your team should have time allocated to technical debt. This might be…
- 15-20% of your time every sprint
- One 2-week refactoring sprint per quarter
Use backlog grooming / backlog refinement and sprint planning meetings as an opportunity to decide how to use your allocated time. I suggest filtering your issues by a theme, such as issues relating to a particular feature, or issues affecting team morale. Try it with Stepsize’s VSCode or JetBrains extension.
Protecting your long-term codebase health starts with nailing the underlying processes surrounding refactoring.
High-performing teams do this by surfacing and prioritising the refactoring issues that will have the greatest impact. Refactoring regularly is a crucial practice.
To make this work, engineers need to consistently track refactoring work that needs doing, and add context to the issues they track. Without tracking as a foundation, it’s impossible to plan and prioritise refactoring work effectively. Tracking high-quality issues, prioritising the ones that will have the most impact and regularly refactoring helps teams avoid the compounding effects of lingering technical debt over the medium to long term.