Legacy code is often treated as a technical debt problem, leading teams to believe that cleaner code or a complete rewrite is the solution. In reality, the biggest challenge is uncertainty. When engineers can no longer predict what a change will affect, every release becomes riskier, development slows down, and confidence disappears. This webinar explores a practical framework for reducing that uncertainty and evolving complex systems without disrupting product delivery.
Speaker
Dmitriy Fedoryshchev is a Lead Software Engineer at EverCommerce with 8 years of experience. He builds systems from scratch, scales them into production platforms, and specializes in modernizing complex software without large-scale rewrites.
The Real Problem Is Not Technical Debt
Messy code is not necessarily bad code. A large or old codebase can still be easy to change if engineers understand how it works. Problems begin when a small modification creates unpredictable side effects, making every change feel risky.
The session introduces the concept of blast radius — the scope of impact a single change can have. As this blast radius becomes larger and less predictable, teams naturally become slower and more cautious. Fear is not the problem itself; it is a symptom of uncertainty.
Why Rewriting Is Usually the Wrong Answer
When systems become difficult to maintain, the first instinct is often to start over. However, legacy systems contain years of accumulated business knowledge that is not immediately visible in the code.
Rules around financial calculations, tax logic, retries, legal requirements, and payment processing often exist for good reasons. Rewriting a system without fully understanding these decisions risks losing valuable business knowledge along with the code itself. Rather than replacing everything, successful teams preserve that knowledge while gradually improving the surrounding architecture.
Reducing the Blast Radius
Instead of pursuing “clean code,” a more practical objective is making changes predictable.
This approach consists of three steps:
- See it — understand the structure of the system and visualize dependencies.
- Map it — identify the real relationships between components rather than relying on folder structures or architectural diagrams.
- Carve it — introduce boundaries that allow individual parts of the system to evolve safely without affecting everything around them.
The goal is not perfection but confidence in making changes.
Finding the Right Place to Start
Many teams attempt to improve the parts of the codebase that look the worst. In practice, appearance matters far less than business impact.
A better strategy is to identify hotspots — areas where high complexity overlaps with frequent changes. These modules create the greatest maintenance cost and therefore provide the highest return on refactoring efforts.
By focusing on hotspots instead of aesthetics, teams can improve delivery speed while minimizing unnecessary work.
Understanding Real Dependencies
Architecture diagrams rarely show how software actually behaves.
Hidden coupling often appears through shared global state, common database tables, configuration files, event ordering, or implicit assumptions built into runtime behavior. Understanding these dependencies is essential before attempting any structural changes.
Tracing a real business workflow from beginning to end often reveals relationships that documentation fails to capture.
Making Change Safe
Before refactoring begins, teams need confidence that existing behavior will remain intact.
Characterization tests capture how the current system behaves, even when that behavior includes known defects. These tests establish a safety net, allowing engineers to improve implementation without introducing unexpected regressions.
The objective is not complete test coverage but protection for the parts of the system that matter most.
Improving Systems Incrementally
Large rewrites are replaced with gradual evolution.
Instead of modifying a complex component directly, teams introduce small architectural boundaries that isolate functionality behind stable interfaces. Once these boundaries exist, individual modules can be replaced one at a time.
Patterns such as Strangler Fig allow old and new implementations to run in parallel, compare results, and migrate traffic gradually, eliminating the need for risky “big bang” deployments.
Avoiding Common Refactoring Mistakes
Successful modernization is as much about discipline as it is about engineering.
Among the most common mistakes are attempting full rewrites, separating refactoring from product delivery, changing legacy code without tests, and pursuing perfectly clean code instead of improving the areas that actually slow the team down.
The most effective teams integrate small improvements into everyday feature development rather than treating modernization as a separate initiative.
Conclusion
The objective of refactoring is not to achieve perfect architecture but to restore confidence in change.
When engineers understand dependencies, reduce uncertainty, and improve systems incrementally, legacy code stops being an obstacle and becomes software that can evolve alongside the business. Predictable change—not perfect code—is what ultimately enables teams to move faster.
