Seven tips you always wanted to know about refactoring
Technical debt is a persecution that sooner or later, at various levels, we are all forced to address. You too will surely have found yourself looking disheartened at your monitor, staring at incomprehensible code produced who knows when, under who knows what premises and conditions.
If you are a human being, your first thought was to wish you were elsewhere.
Your second thought, however, was probably to start refactoring, that is, a total or partial rewriting of the code to improve its quality.
If only you had the time to do this. Because you too will be under similar conditions of unclear and unstable specifications, with an imminent important release, under pressure, always chasing after the next feature.
You would like to
take a few days to improve the code but you can’t. So what next?
Here are some tips that could help you in this common situation.
1. Lower your expectations
The purpose of refactoring is not to have nice code straight away, it is to obtain code that is better than before. Essentially fragmenting a large refactoring into many small self-contained interventions that are objectively improvements and that will help you to decrease your technical debt.
Over time you will see that incomprehensible pieces of code, which would have taken days of work just to be decoded and corrected, will become less and less frightening.
My incremental checklist includes:
- remove dead, commented or unused code
- remove unnecessary, non-descriptive comments that make reading the code more difficult
- improve the name of variables for your current understanding of the code (remember that finding the right name for an object or variable is an iterative process over time)
- apply a coding standard, using automatic tools if possible
- replace magic numbers in the code with constants with a descriptive name
- eliminate the warnings reported by the analysis tools or by the compiler
- break overly large functions into smaller functions with a descriptive name
- multiple functions that operate on the same piece of data can become a class, simply by moving the code
- overly large classes can become multiple classes with more specific functionality
- remove smart code and replace it with clear code without side effects
2. Two-minute rule
A refactoring intervention should be conclusive within a few minutes. Any more time-consuming interventions should be broken into smaller, self-concluding interventions, if possible (and very often it is).
Two minutes to improve the name of a class, or to eliminate a piece of dead code, can always be found.
Refactoring therefore becomes a daily process instead of an extraordinary activity to plan and to be decided with management.
3. Keeping clean is easier than cleaning
Try, if possible, to curb technical debt in input. No refactoring can save you if the speed with which the code degrades is greater than the speed of improvement, and in this case it risks becoming a huge waste of time.
Improving the code is a process that, when
started, requires a certain level of discipline from the whole team.
A very effective tool in this sense is the practice of code-review to identify problems and code-smells.
4. Think about the long run
The fight against technical debt is not a sprint but a marathon. Code slowly deteriorates over time, day after day, commit after commit and in the same way it can improve.
If you can block the incoming technical debt and at the same time achieve a few dozen improvements per week, the results will be visible very quickly.
When, iteratively, the names of variables, functions and classes improve, the dead code disappears, the size of classes and functions decrease, and so it becomes progressively easier to read and understand the code over time.
Improvements that would require hours of work today become easier and less expensive.
5. Use advanced tools and IDEs
To be able to close an improvement cycle in a few minutes it is important to have tools available to help you to achieve this. In particular:
- automatic code formatting tool (e.g. clang-format)
- linter and static analysis (e.g. clang-tidy)
- automatic display of unused code
- IDE with smart rename (e.g. QtCreator, CLion, etc.)
- IDE with code extraction in functions and classes (e.g. Resharper)
6. Architectural interventions apart
Architectural refactoring is the most complicated and the most difficult to break into simpler refactoring. For this reason it is important that all the cleaning work has been performed before addressing architectural elements.
Try never to mix small code cleaning operations (renaming classes, variables, extracting functions, the removal of unnecessary code and comments, etc.) with architectural interventions (communication between components, abstractions, etc.).
Also remember that the latter require that
the direction of refactoring has been discussed and approved by the entire
team. In fact, all team members must be aligned on what needs to be changed and
which design we are going to use.
Ideally these decisions should be written in a supporting document so that they are not lost in development.
7. Automatic tests
Put yourself in a position to be able to
write automatic tests quickly and, at least for all new developments, be sure
to test the various components. This will greatly assist the refactoring
process and help you control stress. The key word is always incremental progress.
Even writing one test per day (which requires very little effort) over time you will find yourself a full-bodied suite.
Using the divide and rule approach, you should be able to reduce the technical debt a little at a time, through a constant series of small interventions of a few minutes.
This should help you build a day-to-day improvement process that will become more significant over the life of the project. All this should be accomplished, as far as possible, in iterations of just a few minutes that do not require special authorisations or alterations in the planning of deadlines.
Refactoring thus becomes a constant, daily process, to always be combined with normal development activities.