Refactoring: At the right time, with the right effort
I often experience that teams are unsure when the right time for refactoring is. They are torn between implementing new features that add value for customers and maintaining the existing system. Even if the decision has been made to invest in software maintenance, the question remains how and in what order to schedule the refactoring steps. This article reflects my experience of what is an appropriate approach for refactoring measures when the decision has already been made to refactor a particular point in the system. The article does not deal with how to deal with technical debt in general or how to balance the further development and maintenance of a system.
What does refactoring mean?
Refactoring is the improvement of the internal structure while maintaining the existing, observable functionality. Refactoring is used when the software system already works as desired, but the internal structure hinders the further development of the system. This can be the case for various reasons. For example, the code can be so bad that it is no longer possible to understand how to incorporate new features into its structure. Or the operation of the system can be so complex due to a bad internal structure that there is little time left for new features.
How can refactorings be scheduled?
Refactorings are necessary waste: They do not bring any immediate benefit to the end customer, but they ensure that the system will remain useful for the end customer in the future. In Scrum, refactorings therefore do not belong in the product backlog. The team is self-organized and therefore has to recognize which tasks have to be completed by when so that the system remains maintainable and can be further developed. However, since refactorings sometimes have a significant impact on the overall planning, I believe that technical tasks such as refactorings should be planned and estimated in the same way as user stories from the product backlog.
It is desirable to divide a large refactoring into small, technically profitable partial refactorings in order to create added technical value more quickly and to be able to react flexibly to changes, new findings, or changing priorities. This way, a refactoring can also be taken back more easily and at a lower cost if you notice that the refactoring is going in the wrong direction. If there are new, urgent requirements, there is the option to postpone the further partial refactorings to later sprints without having to accept disadvantages.
If it is not possible to cut large refactorings into partial refactorings that are useful on their own, the technical benefit only arises after all partial refactorings have been processed. During development, the technical condition of the system is often worsened further because, as long as the refactoring is still in progress, indirections have to be built that connect the old code with the new code. The more of these inductions are drawn, the more the complexity of the code increases. Despite these disadvantages, I recommend splitting the refactoring into sprint-compatible parts. The partial refactorings serve as measuring points to see if the original estimate of the effort is correct and what the progress of the overall refactoring is. Estimating the effort of several smaller tasks is also easier than estimating the effort of a very large task. It is just important to make sure that partial refactorings are not spread over a longer period of time.
When should refactoring be scheduled?
Smaller refactorings are mandatory in agile software development and are therefore performed at any time. Refactorings that reduce unplanned work or lower operating costs have a clear business value and can be easily prioritized against other business user stories. More interesting is the question of when to perform strategic refactorings. In my opinion, it makes sense to prioritize a refactoring immediately before a business story that motivates the refactoring. The reason is simple: If the code is refactored before the technical feature, the refactoring pays for itself, because the refactoring simplifies the code so that the feature can be implemented more quickly. This also means that you don't violate the YAGNI principle, since you always benefit from the refactoring at least at one place and at best at several places in the system.
I do not recommend refactoring after a story, which speculates on a future improvement or use, to avoid the risk of a misjudgement and thus unnecessary work. When finishing a user story, it is sufficient instead that the code meets some basic requirements of clean programming, such as clear communication of the design, reduction of coupling and redundancies, uniform levels of abstraction, appropriate lengths of functions, etc. Through these almost mechanical activities at the end, a design is formed by itself, which in most cases is better than what could be planned and designed in advance.
Further reading
Nathan Marz coined a similar mindset "Suffering-oriented programming" and wrote a great blog post about it.