April 8th, 2021 | by Michał Gorski

Technical Debt in Software Development: How to Minimize It?

Table of contents

As time goes by, software systems tend to build up cruft – unwanted or redundant pieces of code, unnecessary complications, or bad design. That cruft may complicate or slow down adding new features and functionalities or even prevent developing software in the desired way. 

Facing this scenario means you have accumulated a substantial amount of technical debt – and just like financial debt, technical debt can get you into trouble if you don’t pay it back. What exactly is it and how to deal with it?

We answer those questions below. Besides, we talk about:

  • technical debt in a nutshell,
  • what causes tech debt,
  • the types of tech debt and how to recognize it,
  • consequences of technical debt,
  • what to do with existing technical debt,
  • how to minimize tech debt in the future.

What is technical debt?

The concept of technical debt in software development doesn’t have a straightforward definition. It refers to the current cost of additional rework in the code that is incurred due to choosing a quicker (and dirty) or cheaper solution instead of taking a futureproof approach that takes longer to execute. I know it sounds a bit heavy, so here’s a metaphor for you:

You have an old car that requires repair. You can do three things: keep on riding the car, repair it or buy a new one. If you just keep riding it, it may fall apart in no time, and eventually, you will have nothing to ride. In the second case, you need to take time to repair it and pay the price, but the car will likely serve you for a few more good years. In the latter case, you get a brand new car relatively quickly, but you’ll pay a lot more money as well – you’re lucky if you can afford this option. You will still have to wait for your new wheels and that doesn’t always guarantee success. 

It gets even more complex, however. Note that your old car will likely get you to your destination, but what if you need to get there quickly? Your old car isn’t as reliable as it was back in the days. And, you don’t have time for general servicing, which, at this stage, will take a while. Your client won’t be waiting for you forever. 

The investment in the second case mentioned above is what you can compare to paying back the technical debt in software development. It’s about the cost of fixing the accrued bugs so that your system or app could last longer, be more functional, and flexible enough to add new features. In some situations, you will be forced to fix the bad code so it becomes more functional and usable.

Note that, just like in the case of the old car, there will come the point at which investing in renovation or repairs will simply become cost-ineffective and immensely time-consuming, the same will happen to your code: adding new features will become cost-ineffective and time-consuming, if not impossible. For those reasons, it’s good to invest in refactoring on the go, instead of waiting for the moment at which it will be essential.  

Coming back to our old car metaphor: what if you get a new one, but keep on exploiting it in the wrong way? of course, it will break quickly as well. Will you have enough funds for yet another car? Getting a new four-wheel drive will only be a short-term solution if you don’t learn to care for it properly. It’s the same with any software product that undergoes development. The price of ignoring the need of refactoring will continue to increase over time.

CSHARK
CSHARK

If you’re a fan of academic definitions, you can refer to Neil Ernst’s post titled “A Field Study of Technical Debt” published on the Carnegie Mellon University’s Software Engineering Institute blog. He said that:

Technical debt conceptualized the tradeoff between the short-term benefits of rapid delivery and long-term value. Taking shortcuts to expedite the delivery of features in the short term incurs technical debt, analogous to financial debt, that must be paid off later to optimize long-term success.

That’s enough theory for now – let’s see what causes tech debt and what its types are. 

YOUR TECHNICAL DEBT IS GROWING? LET’S FIND THE RIGHT SOLUTION

CONTACT US

Causes of technical debt

There is a common misconception that tech debt is caused by inexperienced engineers who do not know the best programming practices in the first place. Mistakes can be the cause, that’s true, but it’s a little more complicated than that. This misconception only shows there is a misunderstanding of the nature of the tech debt. So what exactly causes it?

  • lack of time for implementing better solutions (working under tight deadlines will encourage teams to cut corners, especially when they don’t know how the app will evolve in the long term)
  • a lazy or unmotivated team that decides to ignore bugs and is generally indifferent
  • absence of a culture of “software development excellence”
  • lack of experience within the team
  • not investing in good design
  • constantly changing or introducing new requirements throughout the software development lifecycle
  • high staff rotation rates
  • passage of time
  • skipping on static code analysis
  • ignoring integration tests
  • Product Owner who dominates Technical Lead
  • lack of Agile software development process (or maladjusted process)

Causes of technical debt

CSHARK
CSHARK

It usually occurs when a business prioritizes speed over quality and chooses a coding solution that is faster to complete rather than a more complicated, and high-quality one. However, care for quality doesn’t guarantee some technical issues won’t accrue over time. It can build up involuntarily due to changes in the development of the system or the project requirements. 

When you need to urgently and unexpectedly fix your code, it’s impossible to predict the consequences of this decision as the project develops. Customers have to react to changing market environments or business priorities so new feature needs often arise in the process.

Types of technical debt

To better understand technical debt and its types, it’s worth looking at the Technical Debt Quadrant created by Martin Fowler. According to this quadrant, four types depending on intent and context. We’re thus looking at a technical debt that is deliberate, inadvertent, prudent, or reckless. Let’s take a closer look at each type:

CSHARK
CSHARK

Reckless and deliberate

This is usually incurred when a development team consciously decides to implement a poor quality solution because it needs to do it quickly. They know there is a better way to do it, but project managers, product owners, or sponsors push for faster results due to the specific business context (most commonly: limited budget). This type of debt occurs mainly in the long term but doesn’t necessarily have to be caused because we chose to meet a tight deadline. This is often true in the case of startups that want to outpace their competitors, but it doesn’t mean they made a bad decision. Sometimes it’s better to release a product with some technical issues instead of allocating additional time and money for developing a perfect tool.

Reckless but accidental

This is probably the worst type that businesses may have to deal with. It implies the technical debt that cannot be eliminated. It is unintentional, so teams don’t usually recognize the moment they incurred it because of inexperience or skill shortage, or because they didn’t have enough time to understand the legacy system. When this type of debt is spotted in an ideal world, teams should usually focus on self-improve – either by training or employing a relevant expert. In that way, they would be able to identify the moment the crisis was incurred and avoid it in the future.

Prudent and deliberate

When you need to cut corners in terms of quality, you’ll end up with deliberate but prudent technical debt. At times, a business simply won’t need to use a better quality solution and/or will be pressed by a deadline, which will force it to give up a better quality deal. In that case, your team is aware of the potential problems this implementation will cause and knows they will have to deal with them later. It’s a good idea to note what technical debt would have to be repaid at a later stage, so it’s done in a controlled manner.

Prudent but inadvertent

Technical debt incurred unintentionally usually highlights that a programming team is in the learning process while working on the project. It’s not necessarily indicative of neglect – quite the contrary, it often occurs despite care for the quality and design of an application. When functionality is implemented or a project finished and the team realizes they could have done some components in a better way, it ends up with prudent but inadvertent technical debt.

Software entropy

While prudent tech debt can benefit your organization, you should be mindful of how much reckless debt you accrue. To make things even more complicated, there’s another type you should know: software entropy (aka bit-rot technical debt). 

This type occurs when software deteriorates over time and starts to lose its usability. It’s like a deterioration process that usually takes longer to develop but can eventually lead to a coding disaster. It’s a real problem that organizations recognize rather infrequently.

Bit rot code can result from implementing small changes in legacy code without fully understanding the impact of that change (mainly reckless but unintentional). As more and more of them are being made, such changes could create enough complexity to alter the entire software. If an engineer violates non-functional requirements, the only way you can fix it is through refactoring. Ensure your code is always using the latest versions of libraries to minimize software entropy.

Looking for a company to modernize your legacy system?

CONTACT US

Examples of technical debt

We’ve already established that the concept of technical debt is not particularly easy to define. Let’s thus take a look at some examples that should make the concept easier to grasp. The Software Engineering Institute identifies the below 13 types in “Towards the Ontology of Technical Debt”:

  • Architecture Debt
  • Build Debt
  • Code Debt
  • Defect Debt
  • Design Debt
  • Documentation Debt
  • Infrastructure Debt
  • People Debt
  • Process Debt
  • Requirement Debt
  • Service Debt
  • Test Automation Debt
  • Test Debt

13 types of technical debt

CSHARK
CSHARK

Some specific examples of technical debt include unreadable, duplicate, or untested code. Out-of-date documentation or spaghetti architecture would also amount to technical debt.

Is technical debt bad?

There’s no straightforward answer to this question. Accruing technical issues is bad in general and can be a source of serious problems. However, if appropriately monitored and managed wisely in a specific business context, it may bring savings. 

In the long term, however, tech debt is likely to increase your operational expenses. It may lead to loss of productivity as you start experiencing system outages (which will also decrease sales numbers!), the inability to make user experience enhancements, or even fines if your tech debt happens to breach security regulations.

Consequences of technical debt

Most of the technical issues are a result of a lack of long-term thinking, or choosing short-term results. Eventually, you will have to pay it back, especially as you start to experience the following:

  • longer time to market as you continue to build the app,
  • reduced software agility,
  • poor security,
  • higher personnel and maintenance costs, especially when you need to hire more people to refactor your application to minimize damages.

As the above keep creeping in and the total cost of ownership starts to increase, you can end up with negative customer feedback and loss of trust. Just like financial debt, technical debt can get you into trouble if you don’t pay it back. If you don’t implement the measures to reduce it, the interest will only keep on growing.

How to deal with technical debt?

However you decide to approach it, always remember to add relevant items to the backlog – in this way, you will allow product owners to address them as a priority in the next sprint. Include enough information that will help understand the issue and the cost of fixing it. Some technical debt will be easy to identify, while others will require some analysis. Here is what you should do to address it:

Assess technical debt

Deteriorating performance releases with loads of bugs or subsequent versions that take longer may be indicative of accumulating technical debt. But how to assess its price? 

You can start by estimating the number of hours your development team would have to spend on refactoring the application or creating a new one. Depending on the time remaining till the next big release, you will then understand if you have the time to repay that debt or not. 

Keep in mind, however, that it may be critical to repay only a part of the accrued debt to be able to move forward. Understanding the development priorities will help you create a “repayment plan” where critical areas have to be tackled first. 

Indeed, at times it won’t be worth the while to repay the debt at all. Think about the debt in a rarely-used area of an application that doesn’t require modifications. Depending on your client’s budget and priorities, it can or can not be feasible to repay that specific part of the technical debt.

Report the debt

Next, it’s key to let all stakeholders know about the debt. This includes communicating the true price of tech debt, its consequences as well as explaining the importance of paying that debt off in the shortest possible time. 

Speak with all stakeholders to identify development priorities and then you will be able to identify critical debt that must be repaid as soon as possible, keeping in mind that you may not have to repay the entire debt.   

Pay off the debt

In this case, you have the following options:

Waive the requirement

This can be your only option if you don’t have the means and a team to fix the existing code, or if you decide that it is the best strategic choice. You will have to waive the requirement and continue operating without it. However, if you cannot afford to waive the requirement, you will have to choose from the following two options:

Refactor your application

The process of refactoring is about improving it while leaving the behavior and functionalities untouched. The goal is to reduce complexities, remove duplicates and improve the overall structure. Further below, we briefly discuss how to approach refactoring to ensure that new bugs don’t appear during the process. 

Replace the application

While this possibility will likely create new technical debt, it will allow you to address it quickly and minimize the new debt. However, bear in mind that this option will probably be the most expensive. It’s not an instant solution but a process. Plus, this Netscape case described by Joel Spolsky in the article called “Things You Should Never Do, Part I”, shows that it’s not always possible to rewrite an application.

Refactor on the go

This is probably the wisest option, as long as you have the budget, time, and resources. Whenever you change a part of a code or add new functionality, ask the developers to leave the code in a better shape than it was, to begin with – always! The changes will be a bit more costly and will take extra effort, but in the process, the structure will improve. If communicated properly, the team will take ownership of the technical debt problem, paying attention to continuously eliminate it.

How to reduce future tech debt?

It’s critical to understand that technical debt must be solved by the entire product team, both technical and business. Work together to establish a strategy and act on it consistently – it’s essential to succeed in this process!

No matter how you decide to deal with technical debt, keep this rule in mind: do not break the existing code. This process doesn’t leave room for any bugs. Once you decide to pay off the technical debt, make sure you do the following:

Build a safety buffer

Before you start, it’s good to have a safety buffer covering the main use cases of your application and catch mistakes for you if they slip your attention. If you don’t have that buffer, you have to recreate one before implementing any changes to the code. The buffer could be relevant regression test plans or unit tests. Definitely speak to your Product Manager to understand all the functionalities and use cases of the app, or you are likely to miss important testing scenarios. 

Test it

After you’ve built your buffer, you must record the current behavior of the system to be aware of any changes and how they affect the code. Automated UI testing, frequency, and content of HTTP calls, API testing, and regression testing will help you understand whether everything is working as expected and that you won’t cause any changes accidentally. Applications like Selenium, Fiddler, or Postman will be helpful in this process.

Improve testability

In the next step, you can isolate specific components of the application to carry out unit tests. Before performing them, you want to eliminate problems like the tight coupling of the code to a database or other resources through dependencies (it can be easily removed through Inversion of Control).

Enhance your Tests

The number of unit tests you carry out at this stage will likely grow, ensuring that they are relevant, clear, and effective. The quality of these tests is often neglected; you have to allocate time and resources for improvements, which will certainly help you reduce technical debt in the long run. Also, use static code analysis tools that will provide on-demand reports of refactoring suggestions to improve the code.

Refactoring

With a safety buffer in place and a thorough testing plan, you’re now ready to apply changes to the bad code. Most development environments have refactoring tools built into that automate most common operations. You can also use external tools for that, but it’s good to trust those automated solutions that won’t get distracted and skip important points. You can try ReSharper if you don’t know where to start with this process.

Expand your tests wherever possible

As you proceed with refactoring your application, you will notice areas where testing is weak. It’s an opportunity to strengthen the process by expanding your tests. You can do this by parameterizing unit test cases, requiring tests during code review, or providing draft test plans to QA. 

Technical debt is an issue that every business must address. If you choose to ignore it, be prepared for the consequences. You may opt to refactor – it will be another investment, but it will bring you the best value for your money if done wisely.

By reducing technical debt and renewing your applications, you, your team, and your customers will stay more agile and competitive. There is no one best solution to scale down technical debt – the best one will consider your product, technology, organization, and business context. You should figure out which solution will work best – do it with your trusted tech partner.

If you’re interested, what can we do for you and what it means for us to deliver more than expected – just reach out!

LOOKING FOR PRODUCT DEVELOPMENT TEAM?

CONTACT US

Michał Gorski

Service Delivery Manager

A Service Delivery Manager at CSHARK. Delivery-go-to person with various tools and approaches such as Project Management, Scrum and Agile. Passionate about value, business goals and communication.