Refactor before you rewrite

Prefer listening over reading? Press play and enjoy

Refactor before you rewrite
8:03

Why Refactoring is important

Refactoring code has a number of different benefits; from clean code being easier to understand & support, to saving time and money, in the future, as it reduces the likelihood of future errors & makes new software implementation easier.

In this post, I’m going to explore the main benefits of refactoring and how you can use it, as a way to take steps towards modernising legacy applications before embarking on a full blown software modernisation project.

I will also look at one of the ways you can get started, when refactoring legacy code and share a kata that will give a practical example to work through.

How companies end up with unmanaged technical debt

Picture a company, it's small, but over the last 10 years, it's grown quickly in its market. Its software product has played a large role in its success. Sure, there are others in the market building similar products, products with a fancy UI and a trendy brand, but for this company's customers nothing can replace the value the software provides. This on the face of it sounds great, lots of happy customers, stable growth and a highly valuable product to back it up.

But, there is a problem, and this problem is also the reason for the company’s success, the product has been quickly iterated on, over the past 10 years, technical debt has been accrued without any intention of paying it back. New members of the team have come and gone, the rapid growth has meant feature after feature has been added without any concern for future maintainability.

This has started to become noticeable: performance issues are abundant, productivity is being affected, features are taking increasingly longer to add, the broken window theory has spread, and the quality of the product deteriorates with each iteration.

Now the company's management is not blind to this. They recognise these issues. New members of staff have been brought on board to help fight the fires, time is being made for quick wins and most importantly of all a rewrite of the entire system has been pencilled in for some time in the future.

Are there steps you can take towards modernising before having business alignment?

Most developers will have worked at least one company that looked a little like this, over the course of our careers, and the approach is often varied.  In some cases the fabled rewrite has happened, sometimes the product has been split into smaller services, creating a distributed monolith and more often than, not the rewrite has been pushed down the road year after year due to financial constraints or lack of confidence in the development team. 

Now, I think all of the above strategies are actually very valid. Each has specific merits for the business.  However, I believe that there is something that must happen before considering the pros and cons of any of the above approaches. That is refactoring of the current code base. 

Where to start when refactoring legacy applications 

What most people would consider legacy applications usually have a few things in common. They use outdated frameworks and patterns, they have little in the way of test coverage, high Cyclomatic complexity, lots of coupling and low cohesion. 

Most importantly the domain logic is scattered across the system. It's this that stands in the way of developers being able to effectively rewrite the software from scratch or even rewrite large chunks at a time using something like the Fig Strangler pattern. The logic that is scattered around the codebase is where the value lies. Without being able to truly understand where this value actually comes from, we can’t hope to rewrite the software effectively. 

So how do we even start to refactor a legacy application when we don’t know where the valuable logic actually is in the first place?

Make incremental improvements when refactoring legacy code

The answer I believe lies in refactoring small parts of the system at a time rather than trying to make sweeping architectural changes that will inevitably break some part of the current logic. By focusing on something as small as one class, method or function at a time we can make small incremental changes that improve the quality of the overall codebase without the risk of losing valuable business logic.

We should mitigate this risk further by adopting a Green, Refactor, Green TDD approach, this can be achieved by following the below steps

  • Write a passing unit test
  • Refactor the code 
  • Ensure the new code passes the test 
  • Refactor the test if necessary 

By following these steps, we can discover what the intended functionality is and improve the valuable parts of the code at a basic level. 

Long complex methods and classes can be broken down into smaller parts. Extraneous logic can be removed and the intent made clearer. All while being confident that the system will still behave as expected in production. 

Had we simply rewritten this part of the system from scratch, we cannot be certain that every path through the code has been covered and that the new system behaves in the same way as the old. 

Practice refactoring using an incremental approach with the Gilded Rose kata

I wholeheartedly recommend spending some time working through the Gilded Rose kata either on Katas or github, using the approach described above while making sure to stick strictly to the defined rules. It’s a great, if not slightly contrived example of refactoring legacy code.

You won’t fix everything that's wrong with this code and that's ok. After the most complex part of the system looks a little better, the rest becomes trivial. If after working through the kata, you are still not sold on the approach, try doing it again, but add the “conjured items” feature first. Which approach did you prefer?

When to use an incremental refactoring approach?

This Green, Refactor, Green approach works well if you are in a position to set aside time each iteration, sprint or week to focus on a particular area of the system. That said, it also works if business constraints don’t allow for dedicated refactoring time. If you practice this behaviour every time you fix a bug or add new functionality will pay dividends very quickly.

As you may have seen from the Gilded Rose kata, it's often faster to refactor difficult to understand code before adding new logic to it than it is to add new logic outright. Not only that, but doing this might encourage others on your team to start doing the same and, before you know it, you have a whole team of people consciously working to improve a product that had previously been viewed as beyond economical repair. 

At this point, patterns begin to emerge, coupling decreases and cohesion increases. The system becomes more stable. Valuable business logic becomes more visible and maintainable. Things that don’t belong in the codebase become more obvious and easier to remove or deal with elsewhere. 

Benefits of refactoring before beginning to modernise

This approach allows the changes to be made in a safe way that protects the existing value of the code. It avoids breaking things while tearing out large parts of the system. Or rushing to reach feature parity on a shiny new version of the product and making the same mistakes again.

To be clear here I'm not advocating software shouldn’t be rewritten and replaced. Neither am I claiming that refactoring is a magic bullet that will right all wrongs. I am sharing how you can create a much better starting point for more aggressive software modernisation  by spending a small amount of time and effort to stabilise and improve small parts of a system. The benefit of this approach is that it can help grow your team’s and business’ confidence in you, the product and themselves.