5 reasons why you shouldn’t recode your product but improve existing one instead

Matthieu Auger
10 min readMay 18, 2021

Over the last 20 years, digital usage has exploded. Websites, mobile applications, Internet of Things. Most companies chose to adapt to this revolution and began to develop new digital products for their customers, on their own, or with service companies.

While the number of digital products has vastly increased they often end up with the same issues: the number of bugs increases; new features are less frequently released; investors and upper management don’t understand why this happens. This is when you may hear the sentence:

We should recode our product from scratch. We learned from our mistakes. We know our users. It will go well.

Usually, it doesn’t. At Theodo during the last 10 years, we built over 1000 digital products for various clients, start-ups, scale-ups, corporates, and the question of whether we should re-use and improve an existing codebase or start a fresh one has been asked many times.

In this post I want to share with you what we learned; why we often think that a complete rewrite of existing application code could solve our every issue; why this process often ends up making things worse on every level and how to reintroduce quality on a legacy project without rewriting the whole application all at once.

An exception to the rule

Before discussing reasons why a code rewrite is most of the case not necessary, there is at least one where it is: technology changes. In the last few years for example, a lot of companies decided to migrate their websites built with server-side frameworks like Symfony (PHP), Django (Python), or Ruby on Rails (Ruby) to client-side frameworks built for Javascript (React, Angular, VueJS). These new technologies offered interactiveness that were game-changer in terms of user experience. In such cases, recoding may be necessary and useful. This post does not focus on such situations but every other ones.

Recoding seems a good option because we don’t understand yet what are the real causes of our problem

A lot of waste in our daily work comes from presuppositions. We use our intuition to analyse complex situations, emit causes on why this happens and propose actions based on these “analyses”. Our brains tend to lean towards intuition as it alleviates the efforts needed in the process of decision-making, making it much quicker and easier than using deep analysis. But intuition is subject to biases.

One common bias is the “Fundamental attribution error”, our tendency to give disproportionate importance to an agent’s internal characteristics (character, intentions, emotions, knowledge, opinions) to the detriment of external and situational (factual) factors.

When we face bugs and slowness in a product we tend to attribute it to the people who developed it: they didn’t have the skills and knowledge to develop a good product right first time.

On the opposite, another known bias is the Dunning–Kruger effect, stating that people tend to overestimate their own abilities.

This combination easily leads to the belief that a full rewrite made by competent people would produce a different outcome: a fully functional and maintainable product. But there are plenty causes to explore before this one.

A first step before choosing to recode a product from scratch would be:

  1. Define and measure your problem: is it related to quality (bugs), productivity (number of features ending in the product)? You should be able to put it on a graph
  2. Go & See on the field to have a better comprehension of your system and understand the real causes. Go on a daily meeting and look at the interactions between team members. Do pair-programming and look at the source code, the developer experience and their environment

Often the real causes of this kind of issues is more about the system that produces the code than the code itself; which can be fixed without a full rewrite.

But before digging in, there are a few more reasons why choosing to rebuild is often a bad take.

The moment when you hear the sen

Rebuilding means less business value for your customers and less feedback for a while

Lean Startup has been a great inspiration for companies on how to build businesses that last. While some of the ideas are still controversial, a major point of the book could be summarised as “Companies that last are the companies that can maximize the number of feedback loops in a short time span”.

This is why agile and DevOps methodologies encountered vast success in companies. They allowed them to ship earlier and more often to their customers, reducing the time required to get feedback on their product.

But the first increment, the so-called MVP often takes weeks to months before being proposed to the users. The first feedback is generally the longer to get.

If you plan to ship a new code version of an existing product, you will surely want to have functional equivalence before launching the new version.

While you are busy rebuilding your product, you will ship fewer features on the existing one. You won’t get new feedbacks. You will reduce your chances to last in the digital ecosystem.

Maybe you’ll fix existing bugs, surely you’ll introduce new ones

A major advantage we suppose with a complete rewrite of an existing code is that developers know where the technical debt is, where the code smells and does produce bugs. Over-use of class inheritance, files with thousands of lines of code, unreadable functional one-liners that no one really understands.

This kind of code must be rewritten indeed. Developers will recode these features much better most of the time and you may see some really well-known bugs disappear and yield great satisfaction among customers and internal teams.

The main culprit here is more about the code you don’t see often. Some developer who chose to add accessibility features to ease keyboard navigation. This form with a specific HTML name that allows users to pre-fill their login.

There is hidden value in live code, often not documented and not tested. When recoding these features, you may forget these tiny bits of code that actually make your product great for some users. On the scale of user dissatisfaction, breaking existing functional features is far worse than the lack of new ones.

This is what happened during the Trainline (ex CapitaineTrain) revamp in August 2020. They decided to rebuild their product from scratch, resulting in a lot of complaints. And this is a matter of how many clicks you need to perform one operation, something that was surely rooted deeply in code but not in a Jira task.

Tweets giving feedbacks on Trainline revamp

You will have the same time constraints, even worse

Amongst other causes, low internal quality often comes from a high constraint on time during the project. Good quality can only happen when developers have time to improve their code and do not focus only on the “functional” part of their work.

When rebuilding a product, you’ll often have less time than the first time you built it. That’s because we tend to think having built it already once will help us go faster, plus “we can’t afford not to produce any value for too much time”.

Maybe at the beginning everyone will agree to say: “right, this time we’ll take time to do this properly”. But then the application maintenance hits. Bugs on the existing product still emerge. New features need to be done on the current version. And you end up with the same features to produce but less time plus the mental load to maintain two different codebases.

Rebuilding fails most of the time because you didn’t fix your main problem: delivering quality, right first time, under delivery pressures

We often hear about code quality: what is code quality, how to measure code quality, what KPI should we measure, what percentage of coverage should we have. But we often underlook the system: in what environment are the developers of the project working? Do they have the conditions to produce quality in autonomy? A few questions you could ask:

The lean perspective on how to produce quality — credits “Learning to Scale” by Régis Medina
  • Work standards : Does your team have a shared and documented vision of what code quality is, for example how variables should be named, how code should be split, how errors should be handled?
  • Knowledge / Skills : Is your team trained to produce good code? Theoretical knowledge is a prerequisite, but practice and supervision bring competence
  • Inputs : Does your team receive user-stories with good description of what need to be done?
  • Equipment : Does your team have performant workstations, tools to automate tedious tasks?
  • Is there something in place to catch code defects before they are deployed on test environments, for example code review by an experienced developer, automated tests?
  • Does your team have frequent discussions about the code itself, where is the debt, what should be refactored?

Before deciding to recode a product, you should try to fix your system first.

You can flatten the quality debt curve

In 2020, COVID hit the entire world. Propagation of the virus has been phenomenal and we saw an exponential increase of new cases and deaths during the first few months.

In France and other countries the strategy chosen by the government was to flatten the curve. The main idea was that with extra actions (lockdown, social distancing, mask wearing) it was possible not to immediately stop the propagation, but slow it enough so our healthcare system can handle it.

The “Flatten the curve” strategy

Quality debt is not that different from a virus. In 2018 Bill Clark, engineering manager and gamer on League of Legends proposed a model for technical debt based on his gaming experience, and one of the axes he proposed to evaluate technical debt was contagion.

To quote Bill Clark, “The third axis is something I’ve become obsessed with: contagion. If this tech debt is allowed to continue to exist, how much will it spread? That spreading can result from other systems interfacing with the afflicted system, from copy-pasting data built on top of the system, or from influencing the way other engineers will choose to implement new features.

Can we implement protective measures to flatten the quality debt curve and limit its contagion?

Strategies to reintroduce quality in a legacy project

The first rule to reintroduce quality in a legacy project and organisation is: you need to talk about quality with your team members.

  • Bring the Product Owner and developers in the same room and ask them: what is a user story good enough for you developers? Should we indicate the page of the feature? The validation procedure? Define what is a good story and write it out somewhere
  • Bring the Tech Lead and developers in the same room and ask them: what is our common vision of a good code? Should we respect SOLID? Prefer pure functions? What should we test? How should we name our composants? Define what is a good code and write it out — our experience shows that the best place for code principles should be as close as possible from the code: in your git repository, or even better in the code itself
  • Make code review mandatory for every chunk of code so that trigger discussion between developers. The Code Review should focus on deep code quality things, not cosmetic ones (“oh you forgot a comma”)
  • Add a technical deep dive before each EPIC (group of user-stories) to create a discussion with your team. Qonto explains it in a practical way in their post “Reintroducing engineering thinking in the development world”. Conceive technical strategies, cooperatively, based on your code standards and document key decisions in ADR (Architecture Decision Records) for posterity
The Qonto way to reintroduce discussions about quality in their daily work

Then make quality easy with examples and tools that help developers code more quality

  • Code components that perfectly respects your code standards and annotate them. At Theodo we use Tyrion to annotate our code when we find debt, but also to show our best code available (we call them “joconde”). A lot of people learn by the example, show them what is a good code
  • Enforce code standards with automated checks. ESLint for example allows you to create your own set of rule. As an exemple we use it to automatically detect potential XSS injections and warn the developer without blocking him
  • Automate tedious tasks. A developer should not spend time to format its code when we have tools like Prettier. Use your IDE settings to automate formatting when a developer saves code

Once you have confidence in your system, you need to do the dirty work and improve existing code, the key here is progressive refactoring. Here are a few guidelines for a performant refactoring:

  1. Code that works and doesn’t need to be changed functionally should not be refactored, yet. If your code works even with an awful code and there is no need to add functionalities, leave it!
  2. Code that need to be refactored should be tested beforehand. No exceptions allowed
  3. Refactoring should be as small as possible. Big refactorings are risky because some developers could adapt the code you are refactoring at the same time. Plus refactoring brings a regression risk so you should split it as much as possible

In the long run, this process will bring you the grail, a healthy, ordered and mastered codebase. You may think that a full rewrite of the code would have brought the same output with less effort and sometimes this is right, but the value here is much more than the codebase, it is the system you put in place to produce code quality at scale.

--

--