“It still works” is not the same thing as “it’s fine.” Most companies don’t decide to modernise a legacy .NET Framework application because someone made a strategic call. They decide because something broke on a Friday afternoon, the person who understood that part of the system left two years ago, and nobody can say with confidence what else is quietly at risk.
Here is how to tell the difference between an old system that’s simply old, and one that has become an active liability, before it forces the decision for you.
1. Nobody Can Explain Why a Piece of Code Exists
Every long lived codebase accumulates logic nobody fully understands anymore: a strange conditional, a hardcoded value, a workaround for a bug in a library version from a decade ago. That’s normal. The risk isn’t the mystery code itself. It’s when nobody on the current team can even investigate it safely.
If a change request gets quietly deprioritised because “we’re not sure what that part does,” that’s not a scheduling problem. It’s a sign the system has outgrown the team’s shared understanding of it, and every future change carries hidden risk.
2. Your Hosting or Runtime Is Approaching End of Support
.NET Framework itself isn’t going away overnight, but the surrounding stack often is: older Windows Server versions, IIS configurations, and dependent libraries all have support timelines. When any of these reach end of life, you lose access to security patches at exactly the moment the application is hardest to touch.
A useful exercise: list every piece of infrastructure your application depends on (OS version, .NET Framework version, SQL Server version, any third party components) and check each one’s support end date. If more than one is within 18 months, that’s a business risk, not an IT footnote.
3. Onboarding a New Developer Takes Months, Not Weeks
This is one of the clearest tells. In a healthy codebase, a competent developer becomes productive within a few weeks. In a system that has become a liability, onboarding stretches to months, not because the developer is slow, but because the knowledge needed to work safely in that codebase was never written down.
// A small but real example of the kind of thing that // only makes sense with tribal knowledge: if (order.Status == 4 && !order.Flags.HasFlag(OrderFlags.Legacy)) { // DO NOT REMOVE. Breaks EOD batch job for the Manchester warehouse order.Status = 7; }
Comments like that are a symptom, not a fix. They are a sign the business logic and the documentation parted ways a long time ago.
4. Every Deployment Needs a Tribal Knowledge Person on Standby
If releases only go smoothly when one specific person is available, not because of process, but because they’re the only one who remembers the manual steps, the gotchas, and what to check afterward, that’s a single point of failure with a name and a phone number. Ask what would happen to the next three releases if that person took a month off. If the honest answer is “we would slow down significantly,” the risk is already priced into your roadmap whether it’s visible on it or not.
5. The System Carries More Weight Than It Was Designed For
Systems built for a specific scope tend to keep absorbing responsibility over the years: more integrations bolted on, more edge cases handled, more departments depending on it, without ever being redesigned for that expanded role. A tool that started as an internal reporting utility can end up, ten years later, quietly running order fulfilment for hundreds of locations. The risk shows up when the system’s actual importance to the business has outpaced the investment made in keeping it maintainable.
What to Do About It
None of this means rewrite everything. In most cases that’s the wrong answer. A full rewrite of a system that has been quietly running the business for over a decade is its own significant risk, and it throws away years of accumulated correctness that a fresh build won’t have. The more realistic path is usually:
- Document the parts nobody understands, before the person who does understand them leaves. This alone reduces more risk than any code change.
- Target the highest risk dependencies first, the OS, runtime, or library versions closest to end of support, rather than modernising evenly across the whole system.
- Get the system onto a supported .NET version incrementally, module by module, rather than as a single high stakes cutover.
- Bring in someone who can read the codebase quickly and work inside it safely without needing six months of ramp up first. This is usually the actual bottleneck, not the code itself.
Legacy isn’t the problem. An undocumented, unmonitored, single point of failure legacy system is. The difference between the two is usually a few weeks of focused work. The trouble is finding time for those few weeks before something forces the issue.

Leave a Reply