Refactor or not to refactor – is that even a question?
“Technical
Debt” is one of the most often encountered problems in software development.
Whether it’s written by the previous owners of the code (damn them!), your team
(ehhh…) or you personally (ooopsie), it’s one of the typical things we
encounter during our work. I have a feeling that there’s an urge in most of the
developers (including me) to adjust and improve existing code. It’s definitely
easier to later understand the code, if it’s written in a more readable and
well tested way. But how to measure if it will be worth the time invested?
When should we refactor?
1. There is a significant risk for
stability of the product
2. Current code base will slow down
implementing new features
3. The framework/library is (soon) not
supported anymore
1.
It’s
unclear what the code does
2. The code has not enough automated
tests
When should we not
refactor?
1. Code that has an ‘expiry date’
2. Code which is rarely changed
3. Code which has unclear requirements
An iterative approach
Core points
Refactor - Invest time now to save it later
Refactoring
can have different goals and it’s important to keep in mind what we want to
achieve with changing existing, working
code.
In the
end, however, the idea is to invest some time now, so that we can later spare even more time.
After all, the goal of every project is always to fulfil a business goal with
a minimal amount of resources. The faster/cheaper it is done, the more
advantage it gives to the company. Looking at it from this point, every corner
cut that’s not followed by more work in the future is a win.
It’s
important to notice thatthe business value of refactoring can often only be estimated by the developers. That means
every time a developer suggests a refactoring, he should be ready to name and
estimate the value it brings for the team, for example:
- Less time spent on implementing new features
- More
knowledge about the current implementation
- Less time needed to test the system
When should we refactor?
When you decide
to do the refactoring, it’s crucial to choose a goal and currently estimate it.
I list here in my opinion the most common refactoring goals. When deciding to
restructure a module, you should choose exactly
one of them, set it as the goal of refactoring and estimate the profit.
Focusing on one goal makes the comparison effort/gain manageable and helps focusing
efforts on reaching the goal.
1. There is a significant risk for
stability of the product
That’s in
my opinion the most obvious case for making a change in the product, even if it
doesn’t bring new features. I’d argue if such a change is really a refactoring,
since it has a clear business value (removing future risk), but if we agree
that it is so, it’s definitely a needed one.
2. Current code base will slow down
implementing new features
In this
case the code works fine, it looks good and it has at least decent test
coverage. However, its current structure makes the implementation of future
extensions problematic and slow. Since it can be unclear, how much we can win
and how much effort is required from the beginning, I suppose an approach with
small steps would be in order here.
3. The framework/library is (soon) not
supported anymore
A living
application being dependent from external code that is not developed anymore
can cause real problems. If the code depending on an old library/framework is
still being developed, it should be considered to put this additional effort
and avoid possible problems in the future.
In my opinion
the previous examples are strong points that clearly bring value, when accomplished. As a comparison, I'd like to also mention examples of some common goals, which are not as strong as the earlier ones - however, they can still be valid ones!
When choosing one of them, one should be much more careful in estimating if the refactoring should or should not be done.
When choosing one of them, one should be much more careful in estimating if the refactoring should or should not be done.
1.
It’s
unclear what the code does
That’s a
common case with ‘inherited’ systems. The team must extend/maintain the
existing product, but the documentation is not full and it’s unclear what’s the
output of the program’s work. With such products it’s important to both gather
the business requirements from experts on the topic and confirm that the code
fulfils them (best with automated tests).
2. The code has not enough automated
tests
This case
is similar to the previous one – this time, however, maybe we have the full
specification of the product or a person, who has that all memorized😉. In any case, if we want to change the code
without risking having to manually test an entire module, we need to refactor
the code and establish a solid test coverage. This costs time, but if there are many
features planned for this product, it’s worth it.
When should we not
refactor?
The following
points are good reasons not to change existing code. If your module fulfils at
least one of them, you should strongly consider avoiding investing too much
time into refactoring the code.
1. Code that has an ‘expiry date’
When a
program is designed to run X times, or untill a specific date, and then shut
down, the need of refactoring is very questionable. Expanding test coverage and
improving readability will give low benefits, as we won’t add many new
features. If the application functions according to the specification, it’s
better to leave the program be and maybe swallow the bitter pill of having to
work with low quality code.
2. Code which is rarely changed
If a piece
of code is rarely changed, the possible gains of refactoring it are little. One
of the solutions would be to hide the code behind an interface and split it
cleanly from the rest of the code. This way the implementation can remain
complex, while not affecting the quality of the rest of the product.
3. Code which has unclear requirements
This one
can be discussed and it’s not always true. However, I’d suggest being very
careful while investing time into changing code where the requirements are at
best shaky. It’s one thing to have to change your code due to unforeseen
requirement changes, but it’s another to invest time into changes knowing that the business requirements are
already unclear. I’d make sure first that we know what our product needs to
do.
An iterative approach
The most
important thing to avoid investing too much time into refactoring the system
before the result is visible. It’s recommended to restructure small modules, rather
than taking on a bigger part of the product.
For
example, if you plan to rewrite the old system, it’s not a good idea to start
writing the new one from the scratch and delivering it, when it’s ready. That
would mean a huge risk for customer – big investment in a new product which
doesn’t need to solve all the old problems or can introduce new ones.
Instead,
you should focus on “pain points”. Start with rewriting a small part of
application which is the slowest – for example, as a tool, which will still
base on the original product. Such intermediate steps help move iteratively to
a new system and constantly bring value to the customer.
Some of
those incremental steps can be Proof of
Concepts (PoC). If the gain of reworking the module are unclear, a PoC or a prototype can offer a fast way
to try the idea out and quickly show it to the customer. After each such step a
decision can be made to follow this path or not.
Core points
To sum up,
there are few core points to follow while deciding on refactoring a system:
- Choose
one goal of refactoring
- Estimate
gain/cost ratio
- Confront
possible reasons not to refactor
- Keep
the process iterative
- Keep
the customers involved and informed
- Deliver results often
Well done
refactoring can be split into small chunks and delivered in an Agile way, even
if on the first glance it’s a huge chunk of clearly technical changes. Choosing
clear goals and involving customer in decision-making process by incrementally showing
him results will help choose the most critical points to be adjusted.
ReplyDeleteMy view
Deliver result often and keep proeces iterative are more or less same points isn't?
Also how do you measure gain ratio? Effort vs gain of refactoring?
Thank you for your comment!
Deletead.1 Let's take an example: we have an insurance system for vehicles: passenger cars, motorcycles and trucks. We want to rewrite the system to be more efficient and scalable.
An iterative approach can ensure that we split and prioritize the parts:
- database model
- backend for cars (both types, let's say the logic is similar)
- backend for motorcycles
- frontend for each modul (x3)
This process doesn't ensure that we show the results as soon as possible. Having an iterative process helps define the lines between scopes, but doesn't help you show the results asap.
By showing the results often we ensure the order:
- define minimal model for motorcycles
- write minimal backed for motorcycles
- write minimal frontend
And we can already show the value. In a perfect case, we firstly define pain points, then do a PoC and then deliver a minimal viable product (mvp).
So maybe we discover that the model is good enough for now and we only need to restructure the backend?
On the other hand, if the model is the real problem, we can restructure part of the model, put a conversion layer between the new model and an old backend and deliver that as our mvp?
ad.2 Exactly. It will always be just an estimation, but it's quite effective if you choose exactly one goal of the refactoring. You can easily check what's the possible gain and clearly compare gain/effort ratio with other proposals or even items in the backlog.