In an ideal world, teams are be highly disciplined with XP practices, and refactoring is part of the Definition of Done for each backlog item; refactoring does not need to be planned separately in the backlog. In the real world, teams may inherit code with a lot of technical debt, or they may have (shame on them!) not been disciplined about refactoring. If your team has enough technical debt to require a significant refactoring effort, how to do you write a good backlog item for refactoring? Does the concept of “user story” even work for refactoring efforts?
First, let’s agree on the definition of refactoring. I like Martin Fowler’s definition:
Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.
I would go further to add that the goal of refactoring is to improve the internal design of code so it is more readable, maintainable, elegant, and simple; in short, so it is easier to change. Refactoring is not a smokescreen for fixing a bunch of bugs. It is not any effort to change functionality. While it may result in performance enhancements, that is not the primary goal: the performance of a system is form of external behavior.
Before starting a big refactoring effort, be sure to ask question #1: When is the right time to undertake a major refactoring effort to pay down technical debt?
Should you embark on a big refactoring effort at the end of a project, before a production release? This could be risky without a very comprehensive set of automated tests. How could you recover if the refactoring causes more harm than good? Would you be better off starting the next release with the refactoring effort? Note: I credit my colleague Aidan Ridley with identifying this important first question.
Here are some guidelines for preparing backlog items for refactoring.
- Keep refactoring tasks small (the “S” in INVEST). Identify a single small component or even a single class as the scope of a refactoring item.
- Use metrics as indicators for refactoring targets. Measure code complexity, coupling & cohesion to identify good candidates for refactoring.
- Prioritize refactoring backlog items based on the value-t0-cost ratio. The value of a particular refactoring item may be known subjectively to the team, or may be indicated by metrics.
- Define an adequate unit testing standard before starting. Write all the unit tests required to meet the standard before doing the refactoring, and use them to validate component functionality after refactoring.
- Identify the components and functionality that may be impacted by the refactoring, and create a test plan accordingly.
- Create automated system-level tests (smoke tests, integration tests) in order to validate whole-system functionality, particularly for areas identified in the previous step.
- Identify any open defects that may be fixed by a refactoring. Coordinate with the team on any defect-fixing efforts that might overlap with the refactoring effort.
- Identify any dependencies between backlog items – refactoring or otherwise. Can you find ways to break those dependencies, such as changing the scope of an item, or creating interfaces between components to isolate work in different areas?
- Do you need help convincing the Product Owner to prioritize the refactoring effort? Try to quantify the value of the refactoring. How much would the team’s velocity increase as a result of refactoring? What reduction in defects would you expect?
I must give credit to some of my colleagues for helping with some of the ideas in this post.