Technical Debt; The 10x Way of Addressing It

Case study on an early-stage startup’s React.js front end

Ioannis Papadopoulos
The Startup
4 min readSep 3, 2020

--

Image by Unknown

You blame high technical debt for low velocity. You get asked: How much time do you need to address it? Unknown. But when will you start seeing a tangible improvement in productivity? If you do it right, from day one.

This is a compilation of practical advice on how to get the fastest, front-loaded ROI during this process.

Choose your refactoring battles wisely

Identify

Use product-driven bugs, new features or change requests as opportunities to identify refactoring candidates.

Track

Prefer a tool that facilitates communicating this work with the business and provides transparency.

Divide

You don’t have to refactor modules or even components in full. Break work down to smaller tasks and prioritize separately.

The same principle applies for new code too; it doesn’t have to be “state of the art”, it’s okay to reuse patterns that you are planning to change but not just yet.

Prioritize

Some of the parameters you’ll need to consider:

  1. The frequency of change requests
    If a certain feature is not frequently touched, the value in refactoring it, is minimal.
  2. The product roadmap
    Refactoring a soon to be a deprecated module, even if it’s frequently changed, could be mostly a throwaway.
  3. Onboarding and mentoring
    New developers tend to follow patterns found in the existing codebase; this is acceptable and desirable. Removing code smells from modules that you use as a reference can save you and your team time.
  4. The cost of identifying, tracking, and prioritizing itself
    If you see a quick win, it’s better to go for it immediately and avoid the overhead of re-identifying, tracking, and communicating the issue. Just be sure it is indeed quick.
  5. Future technical debt
    Adapting to an evolving product and new technology, or even to your own improved skills and domain knowledge means that regardless of how much thought you put on a design today, you are also introducing the technical debt of tomorrow. Avoid refactoring something until you see clear value in doing so.

Our phase 1 refactoring was a mix of critical architectural issues (e.g. extensive prop drilling — poor component wiring), quick wins (e.g. converting magic numbers to semantically named constants), and building the most commonly used core components (buttons, form fields, etc).

One particular strategy was very effective during that phase; upon discovery of a group of convoluted and highly coupled components, we identified the entry point/interface and optimized just this. Thus, we facilitated reuse of these structures while their inner cleanup was deferred, hoping that in the future we would have built our test coverage up and it would be done more reliably.

Looking for some quick wins? Here’re 5 good tips that mostly aim to reduce cognitive complexity.

Regressions

You’ll often hear that to refactor some code, you should start by covering its functionality with automated tests first (if there aren’t any). We couldn’t do this. A trick to mitigate the risk of regressions is to always combine refactoring with some product-driven work. Then QA and UAT for that work will naturally cover the refactored code too.

Test coverage is more than a number

Going from 0% to optimal test coverage takes time. During this journey, whatever your coverage is, make it as worthwhile as possible. Here’s how:

Start with integration tests

They will cover larger areas of functionality more quickly and provide you with more confidence. Read Kent C. Dodds’ blog for more best practices in testing.

Prioritize which components should be tested first

Some things to consider:

  1. Complexity
    Overly complex components are more susceptible to regressions.
  2. How popular is the user journey that the component belongs to?
    A regression that manifests during a very common user journey can quickly and severely hurt the product’s credibility.
  3. Business-specific importance factors
    If an admin-only platform configuration form is the first thing a user does when evaluating your product, although it doesn’t affect many users, it still could be relatively high-priority.
  4. Previous regressions
    Users are less forgiving when it comes to repetitive issues on things that they have already brought to your attention. The rule of thumb is that you should write tests asserting every bug fix you deploy.
Our most complex components — not all of them belong to popular user journeys

Monitor and Measure

Yes! If you’re doing this right, your team velocity should be increasing every day and after a few months, the improvement should be significant.

If you really want to see the debt reduction progress in numbers, you can set up some tools, besides the test coverage report, that monitor the health of your codebase. This could also reinforce the business’s confidence in your approach.

We’re using both the CodeClimate CLI and SonarQube. Both of these tools come with specific suggestions about what should be refactored, however, we mostly use them to get summaries and check how the code quality is trending over time.

Our Test Coverage & Code Climate report history

We went from 1490 issues in February 2019 to 786 in May 2020 (-47%). The results are even more impressive if we focus only on those categorized as “Major” (-57%). Note that despite the continuous development of new features, the total lines are down by 18%, which is another by-product of the ongoing refactoring.

Takeaways

Prioritize, prioritize, prioritize.

Addressing your technical debt means that you’re working today on improving your tomorrow’s productivity. In this context, timing is more important than anything else.

References

https://kentcdodds.com/

https://sourcery.ai/blog/five-refactoring-tips/

https://github.com/codeclimate/codeclimate

https://www.sonarqube.org/

https://jestjs.io/

--

--