A common case of refactoring-gone-wrong is when refactoring becomes a large project in a branch that can never be merged because the refactoring project is never completed. The refactoring project is considered a separate project, and soon starts to feel like "The Big Rewrite That Always Fails" from programming literature.
The work happens in a branch because people actually fear the change. They want to see it before they believe it, and review every single part of it before it can be merged. This process may take months. Meanwhile, other developers keep making changes to the main branch, so merging the refactoring branch is going to be a very tedious, if not dangerous thing to do. A task that, on its own, can cause the failure of the refactoring project itself.
So can't we use a branch for refactoring? Of course we can. But it has to be a short-lived branch. How can you ensure that a branch is short-lived?
- It has small commits, created within small time intervals (e.g. minutes, not hours)
- Each commit passes all the tests (meaning the actual tests pass, and static analysis yields no errors)
- The branch can be merged and deployed at all times (and actually, should be merged regularly)
Following this set of rules is a great idea for any branch, not just refactoring branches. But it's even more important there, since the changes are likely to span many, and remote parts of the code base, which makes the risk of merge problems bigger.
What often happens is that we change a method in a way that requires updating all its clients. It takes a lot of time to do this work, and so we end up with either a very large commit, or a commit that just takes a lot of time to make, meaning that we don't follow the first rule of short-lived branches.
Something else that could happen is that we are just viciously updating code all around the code base, and we commit the changes because everything seems alright, but then our quality assurance tools tell us something is wrong. When we get the results back from CI, we add another commit that "Fixes tests" or "Makes PHPStan happy". When working with short-lived branches, ensure that everything is okay before committing (or set up a pre-commit hook so you can't forget to do this).
What if we have to stop now?
Creating small commits that pass all the tests, the result should indeed be that our branch can be merged at all times. This for me is closely aligned to a thought I always have in mind when programming: what if someone pulls the plug on this project today? I don't want my effort to be wasted, I don't want my branch to be deleted without merging. So when I work on something I always aim for it to be useful for the team, the company, its users, etc.
One way to make sure that you always add value to the project is to establish goals for which the following is true:
- The bigger goal can be reached in a number of smaller steps
- Each step is useful when considered on its own
We'll take a closer look at refactoring goals in the next article.
Refactoring projects require short-lived branches, where every commit can be merged in the main branch immediately. You should be able to stop the refactoring project at any time, while still leaving the project in a better state.