Tech modernization: gracefully managing poor code and refactoring
Tech modernization stands as one of the most intricate obstacles faced by software teams today. It involves not just updating legacy code; it requires transforming systems to enhance scalability, performance, and long-term sustainability without sacrificing progress on product objectives.
Developers typically understand what needs fixing, but determining the right moment to tackle those concerns can be challenging, particularly with backlogs filled with enticing new features. Inheriting legacy code that is poorly documented or built on outdated assumptions adds to the challenge of effective technical debt management.
We've all experienced the temptation to rewrite everything from the ground up. However, effective technical debt management is not about starting anew; it focuses on implementing thoughtful, incremental enhancements that deliver genuine business value while ensuring product development continues.
Years of experience temper the urge to rush into solutions: managing legacy code is part of the job, and avoiding it is rarely feasible. An experienced professional can better evaluate the benefits derived from engaging with these challenging issues before investing substantial time and resources in a comprehensive legacy code strategy.
However, a key question arises: How can we persuade Product leadership that the moment to confront technical debt has arrived? This is especially difficult when those in Product leadership lack technical expertise. Furthermore, how can we quantify the precise value of addressing technical debt before we put solutions in place?
At Vinta, we've gathered insights from our decade of experience as software engineers focused on prioritizing technical debt and effectively conveying its importance to stakeholders. By examining this resource, you'll gain valuable insights into seamlessly incorporating debt management into your team's daily operations, preventing it from becoming a neglected concern through proven tech debt best practices.
Strategically integrating technical debt management: priorities, metrics, and outcomes
A successful approach to how to manage technical debt must have clear objectives that must be included in your strategy. With defined goals, measuring progress, allocating resources effectively, and ensuring alignment with overall project objectives becomes easier.
There are several objectives you can choose from when developing your legacy code strategy, such as:
- reducing the cost of developing new functionalities integrated with existing ones;
- improving team performance by reducing cognitive costs;
- reducing the maintenance cost of a part of the code;
- improving the app performance itself;
- reducing the time spent on support;
- reducing the risk of incidents.
Engaging with leadership is crucial to determining the priority objectives during your team's strategy definition. By understanding these priorities, you can accurately assess the current state of each objective and establish clear goals to achieve them. This makes prioritization easier and ensures that efforts are directly aligned with the strategy.
Measuring the results will give substance to the practice and reveal the impact of "invisible work" on the strategy. This not only fuels the morale of the technical team but also helps to gain the leadership's trust regarding technical matters. For example, when we worked with Carta during their high-growth period, we were able to demonstrate measurable impact by reducing their open bugs by 60% within the first month—concrete proof of technical debt resolution's business value.
There are many popular ways of measuring team throughput and stability, for example:
- Metrics for DevOps: DORA;
- Team performance: Delivery Time, Review Time, and Deploy time;
- Tracking code reviews quality: Number of comments in PRs, number of changes per PR;
- Tracking outdated dependencies: LibYears.

All those metrics can be used as arguments for prioritizing technical debt solutions, but they don't come for free. It takes considerable work to measure them and compare their statuses before and after changes. In addition, it's often impossible to forecast what results to expect before implementing changes.
We might have a pleasant surprise, significantly impacting key indicators; however, there's always the possibility that the outcome will be different from what we initially planned, resulting in wasted time on something that didn't yield significant results. Nevertheless, there are ways to reduce this risk, such as involving more action and less strategy in your tech debt best practices.
Mitigating technical debt: practical insights and implementation strategies
Here at Vinta, our co-founder Filipe Ximenes once made an analogy that we've adopted for life: he compared software developers to chefs.
When you order something in a restaurant, a lot happens behind the scenes before the meal reaches your table. Chefs might master many practices and processes to guarantee we have the best experience possible, practices and processes we had no idea existed.
For instance, a chef knows that to avoid cross-contamination, they cannot use the same pans and utensils for different foods. As a customer allergic to seafood, you trust all precautions are being taken in the kitchen so that your Chicken Parmesan does not come with residues of shrimp from the Seafood Stew. You don't directly ask the chef to wash the pan or to use a different one; you just trust they will do that.

This also applies to software development: Developers, particularly those more experienced, have various practices and processes that are abstract to people from other areas. This gives them the responsibility to ensure that the software is built with the requirements for the product's success, such as developing tests, investigating bugs, implementing improvements in the development environment, documentation, and refactoring, among others.
All these activities impact development time and should only be done with criteria. As developers, it is up to us to know when to include them in our day-to-day work, seize opportunities, and ignore them without many side effects when balancing refactoring vs shipping priorities.
When we discuss larger technical debt issues, we face a more significant challenge. Including them in our routine is even harder without impacting the project timeline. In these cases, we have to break debts down into smaller parts and use tools such as proof of concept (PoC) and other experiments to prioritize, define efforts, and estimate gains.
These experimentation and evaluation activities usually require little effort and can be done daily without asking anyone for permission. This is part of our work as much as it is for the chef to wash their hands before cooking. And the chef doesn't ask for permission to wash their hands.
These tools give us inputs to argue with leadership and include greater efforts for technical debt solutions in the product strategy with confidence in their effectiveness.
In summary, it's essential to perceive the evaluation of technical debt as a discipline within our profession. We should integrate it into our routine to build confidence in executing larger projects that may not directly relate to the product's functional aspects but still align with the overall strategy and legacy code strategy.
Technical debt isn't necessarily bad
Measuring the impact of software quality and productivity can be challenging. Yet, studies like "Code Red: The Business Impact of Code Quality" by Adam Tornhill and Markus Borg provide compelling evidence. According to their findings, investing in software quality yields significant benefits because it leads to fewer defects, faster delivery times, and increased customer satisfaction.
So, how do we justify introducing technical debt into product development when considering refactoring vs shipping decisions? We believe the keyword here is "opportunity."
There are many unproven hypotheses and opportunities in sight (which are usually the main motivations for developing the product), especially in the early stages of a product.
Investing excessively in quality during these early stages can lead to higher long-term development costs. However, it may also result in delays in releasing the product, causing it to miss the optimal time to market. In such scenarios, it becomes imperative to carefully evaluate the trade-offs and determine which sacrifices can be made to capitalize on immediate opportunities, recognizing that certain implementations may need to be revisited as part of your ongoing legacy code strategy.
Known concepts like Clean Code's YAGNI (You Ain't Gonna Need It) and KISS (Keep it Simple, Stupid) reinforce that premature optimizations and abstractions can undermine time-sensitive opportunities and create unnecessary complexities in your software.
Opting for simplicity and implementing only essential features typically prevents an increase in the team's cognitive load and maintains productivity. Yet, you should be careful about how deep you should go regarding the application's future prediction and your attachment to specific architectures and solutions.
Frequently, the advantages of having a product ready sooner can yield crucial value for reinvestment despite awareness of potential future challenges with the introduced code. This code can subsequently be refined with a larger and more capable team. Failing to be pragmatic about what is essential for the product's success can result in its discontinuity.
Prioritizing technical debt in practice
As mentioned earlier, learning how to manage technical debt effectively requires that it either be affordable enough to integrate into our daily workflow or closely aligned with the product strategy. To identify priorities in your backlog, it is necessary to pay attention to the following factors:
- Impact on team productivity and cognitive cost: complexity in architecture and legacy code often generates difficulty in frequent tasks. The higher the complexity and frequency of activities impacted by technical debt, the greater the priority it should receive in your tech debt best practices;
- Impact on maintenance: debts that generate data inconsistencies or problems in important user operations, especially when these occurrences require manual work to be solved, are strong candidates for prioritization. The frequency of occurrences and the time taken to find the solution are critical factors for the debt solution to be taken seriously;
- Security impact: if technical debt puts the product at risk of potential attacks or in the crosshairs of malicious individuals, the criticality and probability of exploited vulnerabilities should be considered in prioritization;
- Scalability impact: if there is an intention, pretension, or forecast of a considerable increase in the number of users or the use of specific functionality, the impact of debts that affect performance should be evaluated. Tools like load and stress testing are essential to understanding the impact and prioritization of technical debt solutions;
- Development cost: technical debt solutions with low costs can be implemented between activities or during pauses during lengthy tasks. However, those with higher costs should be broken down as much as possible into smaller deliveries, each with its impact assessments. Activities that can no longer be divided should be estimated so that the cost is considered in prioritization;
- Opportunity cost: when working on a problematic part of the code, considerable effort is involved in understanding these problems and thinking about possible solutions. When this time is spent for whatever reason (whether it's an activity that integrates with the problematic code or even the solution to a bug), there is an opportunity to solve it without recurring this cost again. We are not always available to do so, and the opportunity cost doesn't always justify immediate prioritization. Another opportunity is when we know we will develop something that uses code with technical debt, and we can reduce the development cost if we solve the technical debt beforehand.
The combined impact of these factors will determine the importance and urgency of addressing technical debt. However, the degree of consideration given to each factor will depend on the product strategy and your chosen approach for refactoring vs shipping decisions.
A healthy way to prioritize activities is to create a formula that considers the importance of each factor for your product and a numerical measure that represents the level of impact or cost of each.
The numerical measure can be more factual, like the number of estimated days to complete the activity, or it can be more abstract, for example, 1 representing low cost, 2 representing medium cost, and 3 representing high cost. Whatever works best in the context of the product and the team.
Special attention is recommended to keeping dependencies updated as part of your legacy code strategy. This can influence your application's security and significantly impact your team's performance with almost no effort. This performance improvement can occur because, with updated versions, we have access to new functionalities that can simplify the use of dependencies and enhance the development experience. The effort is low because implementing these features would be much more costly than updating the libraries.
Performing this analysis routine will make technical debt prioritization more manageable and integrated into your team's daily workflow. Additionally, articulating concerns, risks, and opportunities aligned with the strategy becomes more straightforward, employing pragmatism and presenting arguments easily digestible even for non-technical stakeholders.
Communicating the importance of prioritizing technical debt with product stakeholders
The most challenging aspect of how to manage technical debt is persuading non-technical stakeholders of the need to revise outdated code. There are various techniques for allocating time for the development team.
Throughout our career at Vinta, we've observed teams utilizing the Portfolio approach, which allocates a specific percentage of the workforce to focus on making improvements. This method can be effective for products with numerous small to medium-complexity issues, as these can be addressed weekly or monthly. However, we've identified two significant challenges that could make this dedicated time for addressing technical debt problematic:
- The flexible definition of Technical Debt: It's typical for non-technical stakeholders to fit features and support tasks into the technical debt bucket. This makes us use the technical debt portfolio for other things with a more visible impact on the product strategy, while the actual technical debt doesn't get its place in the backlog.
- The product development pace cannot decrease: when your team has more significant issues to look at, like architectural changes or module replacements, the Portfolio approach tells you to accumulate time without working on technical debt and then use the accumulated time to fix it. This strategy looks OK in theory, but reducing the speed for product updates may disengage part of the user base and create pressure for the product managers to deliver faster. In practice, you might not even have the opportunity to utilize the accumulated time due to shifts in priorities or the relentless pace of ongoing tasks.
In general, you'll end up dealing with the same issue of not having saved time: convincing the leadership of the importance of revisiting legacy code. The difference is the negotiation would now involve a lot of mental gymnastics and stretching of the product rope.
The best approach to implementing effective tech debt best practices is to not negotiate at all, but instead just be transparent about the benefits of investing in technical debt and share the risks of not doing so. This means more work because your team needs to assess the impact of each task (if it's big enough, of course), but it also means you'll speak the same language as the leadership: product strategy.
.webp)
Mastering technical debt: key takeaways and next steps
Effective technical debt management requires treating evaluation and prioritization as core disciplines within software development. By integrating these practices into your daily workflow and communicating their strategic value to stakeholders, you can confidently tackle larger projects that ensure long-term product success.
Here at Vinta, we understand the challenges of balancing technical excellence with business priorities. Our team has extensive experience helping companies develop effective strategies for how to manage technical debt while maintaining development velocity.
Whether you're dealing with legacy system migrations, architectural improvements, or need guidance on implementing tech debt best practices, we'd love to help you navigate these complex decisions.
Don't forget you can easily download this valuable resource as an ebook for convenient access anytime, anywhere. Take the next step towards mastering technical debt management and ensuring your team's success. Click now and equip yourself with the tools needed for smoother software development.