Dealing With Legacy Code - How To Make Anything More Testable

As software developers, it's inevitable that if we hang around long enough that we're going to encounter this. In fact, you've potentially faced this numerous times already--Dealing with legacy code. I know that just reading that probably made your stomach feel upset. I get it.

One of the big challenges when dealing with legacy code is fixing up bugs. Software engineers are incredible problem solvers though, so when faced with a challenging bug in legacy code, there's motivation to tackle something challenging. But the problem doesn't stop with the bug and the bug fix because it's all about what comes after. The tests.

In this article, I'll share with you my extremely simple strategy for making anything more testable. No more "It can't be tested" when dealing with legacy code. Instead, it should turn into "is it worth us testing". Let's go!

Note: This post is part of the 2023 .NET Advent Calendar! Check out the rest of the entries!


What's In This Article: Dealing With Legacy Code

Remember to follow along!

// FIXME: social media icons coming back soon!


Why Dealing With Legacy Code Is Challenging

Before I explain the strategy here, I think it's important to set the stage for why dealing with legacy code is challenging. It's a little bit more than just "I think the code is messy" or "it's totally spaghetti over there". This is *part* of it, but it doesn't fully explain it.

Legacy code, in general, moves against the few things we try to optimize for:

  • Extensibility: Often the architecture was well suited for the time and as things evolve, it becomes less extensible. Adding new features becomes more challenging without having to refactor or retrofit aspects of it. It doesn't matter if you started off with clean architecture in .net core or hexagonal architecture for your ASP.NET application - whatever you picked just isn't lining up with the direction you need to move in.
  • Low Coupling: Over time, more and more things eventually become coupled. It's always good to assume that the original developers did their best to reduce coupling, but over time more things sneak in. Without lots of time to refactor, these things can accumulate.
  • High Cohesion: Same as the previous statement. Eventually without keeping up on tech debt, different modules of code can start to lose cohesion.
  • Testability: One of the big ones for us in this article's focus is the testability. Given some of these other constraints, the testability is often low.

And not to mention that when we have all of these conditions working against us (and surely there are more not working in our favor), the odds are that we have low test coverage to begin with. This leads to a recipe of low confidence when making changes.