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?

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.

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.

Comments


  1. My 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?

    ReplyDelete
    Replies
    1. Thank you for your comment!
      ad.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.

      Delete

Post a Comment

Popular posts from this blog

Behavior Driven Development & Testing

By failing to prepare you’re preparing to fail