Creating software is a long and complex process, during which some functionalities clarify themselves as the team accumulates domain knowledge and gains technical know-how. It’s often only at the end of this road when it becomes obvious to the software engineers what would’ve been the best strategy to develop the application, and what could have been done better.
Starting over isn’t always possible, thus we rely on refactorization. It serves the purpose of increasing the software’s quality, and confirms that the architecture and the codebase represent the experts’ knowledge and procedures as closely as possible. In the long term, it resolves internal inconsistencies and increases software stability.
Neglecting this has the opposite effect. Ignored for too long, it slows down the delivery of new features and requires more resource-draining maintenance. With the increased number of tweaks and unexpected interconnections, the probability of making mistakes rises. As a result, regression errors occur.
This doesn’t exclusively affect the code. Debt-laden infrastructure can negatively influence an application’s scalability or simply generate much higher costs. Focusing on eliminating the debt earlier might alleviate some of its shortcomings. How can you do that? Read on to find out!
What is technical debt?
We do not have a formal definition for technical debt. Even people working in the field, when identifying the issue, rely on previous experience and intuition.
But thanks to the ubiquitousness of the term, everybody in the team – programmers, managers, QA, product owners – can understand the general idea behind technical debt. Indeed this brings a shared level of understanding as everyone can use the debt metaphor to point out issues that with the course of time will accrue interest.
The metaphor of technical debt became well known in the IT world thanks to Ward Cunningham. He used it for the first time to explain to non-technical management his reasons behind scheduling a refactor effort in a financial software he was working on back then. As Cunningham himself put it:
Shipping a first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid (…) Entire engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation.
A technological debt, in opposition to a financial one, isn’t always controlled by clear-cut rules. From a perspective of a non technical person it might suddenly appear in an application that has been successfully running on production. Introducing even the smallest change is followed by a cascade of unexpected issues that, in a worst-case scenario, might bring development to a standstill.
The development team has to make balanced decisions that will result in a stable application – free from serious technical debt. But the team also has to remember that an application is also a business product, encumbered with certain expectations from stakeholders. Putting the effort purely on delivery of the new features, without giving a thought to how they fit in with the current architecture or their future direction, is one of the hot spots for future technical debt.
How the tech debt manifests itself
Technical debt often starts accruing while the application is still in development. According to the results of a survey carried out among IT professionals with years of experience employed as programmers, managers, architects, QA and others, technical debt can be noticed in:
- bad architecture
- overly complex code
- lack of documentation or an insufficient number of tests.
Bad architecture decisions could be caused by lacking full comprehension of the business domain. This reflects on the application’s structure or infrastructure. Neglecting this part is indeed one of the most difficult problems to fix later.
Code complexity can quickly become an issue for the developers – though it’s something they have to deal with almost everyday. Some level of complexity is natural for software, as it’s required for it to properly perform its tasks. But if the team is guilty of bad practices, lack of time and misguided feature chasing, the code can easily become overly complex.
Documentation and tests are often victims of a strained schedule. The documentation always needs to be up to date, which requires a lot of care and nearly constant updates. In the case of smaller applications, the lack of tests can be alleviated by a good QA team. It can often catch potentially fatal functional bugs within the app.
It’s important to note that even the worst and patch-worked codebase can still compile into a „working” application. From the outside, it might look good for a while, unless we have to touch it and introduce updates, which is usually when everything starts to fall apart. A lot of such issues might stay dormant for a long time, and only begin to cause problems much later. Therefore, if at any point the Dev Team signals that it’s a good moment to go back and do something right (ie. refactor) – it might be worth listening to them.
There is a place for the technical debt in the process of app development, mainly when it’s incurred as a result of informed decisions and compromise has been made with a purpose.
Such debt is a part of a long-term strategy. Acquiring it allows the team to move forward more rapidly, and transform any shortcomings into stable implementations later. On the opposite side of that is the short-term approach: it speeds up the development process during the initial stages, but can greatly hinder evolvability afterwards.
Similar to financial debt that we can spend to bootstrap our business, for technical debt we also need a strategy to responsibly manage the resources we have gained by incurring it and, most importantly, have a plan when to start paying it off.
One of the popular taxonomies was proposed by Steve McConnell. He described two types of debt:
- type 1 – non intentional – concerning mostly the codebase and quality of other programmer artifacts (files, decisions, architecture).
- type 2- intentional – which allows us to gain measurable business benefits, in exchange for incurring it. For example, by faster time to market, defending the position on the market, shortens the delivery time of new features.
Martin Fowler has proposed another division. The Technical Debt Quadrant – in which he presented relations between developers and the debt. He notes that the debt can occur through decisions made thoughtfully or not (reckless – prudent); intentionally or accidentally (deliberate – inadvertent). This sets up the following combinations:
The debt isn’t inherently bad. As long as we strive to make thoughtful and intentional decisions, we can gain measurable value from it and convert tech debt to money.
MVP and disposable code
We can consider an MVP as a case when incurring debt can be beneficial, where the key is focusing on the delivery of an operational application and delaying the repayment. Thanks to that we can iterate faster, getting the product to users, and keep tweaking it to best accommodate their needs while the debt increases.
Going one step further, we could look at the entire MVP as a one-time use object; squeeze from it as much value as possible, and rewrite it entirely afterwards. After testing the market, pivoting, and finding our niche; the business should end up with a greater understanding of what their product is supposed to do and what exactly their users need.
Also, the development team will have mastered the domain knowledge and gained a lot of experience while working on the first version. They are well aware of what went right and wrong, and most importantly how to make it better. As a result, the team is now capable of coming with much more well thought out and adjusted architecture.
In such a case the issue of technical debt is not important, as we will never have to repay it. This approach can be called sacrificial architecture.
The importance of communication
Clear communication is the key factor here – the dev team must be fully aware they can move fast and take riskier routes, as the codebase won’t be maintained. The client, on the other hand, has to understand the advantages and disadvantages of this investment strategy – faster time to market, rapid pivot, smaller cost of pulling out of the market. How much you need to worry about paying off the debt depends in what direction you want to direct the system.
How to carry out a code audit
The application lifecycle isn’t always clear. Most often we reuse previously written code or come into ownership of a completely new application without a supporting team. Even if we made an effort and managed our debt the best we could, there might still be some concerns.
This can manifest itself in various ways: the processes take much longer than before, the deployment is so complex it requires developers on standby, or we’re forced to push numerous hotfixes to the production in order to deal with unexpected bugs after the release. All of these things are a sign of a deeper issue.
In order to identify what exactly went wrong, we can do a Code Audit. The audit process can be compared to a code review. Similarly, we verify the code quality, pinpointing problems and code smells.
The audit can be performed by the same internal team, though there are some benefits to getting a 3rd party team to do that. Contracted professionals aren’t immersed in the project development process yet. They are capable of looking at the product from a wider perspective, reaching outside the code layer.
The audit process should investigate the state of the used technologies. Check whether versions of all libraries and software are up to date and are free from any vulnerabilities. Verify the project architecture, and if the code follows modern standards and doesn’t possess obvious security risks. Infrastructure can also be a subject of audit; for example, reviewing the cloud services and where the resources are chosen correctly and aren’t generating additional costs.
The auditors gather their results in a document. It can suggest how to resolve problems, or even shed some light on future steps the app should take to operate better. If it’s part of the contract, the team can begin working on fixing selected issues highlighted in the summary. In such cases, the documentation will be expanded with descriptions of what was done and how it affected the system.
Tech debt in software development: summary
Technical debt can harm the project when it’s not properly managed. Still, it’s a natural part of software development and with accurate efforts, it can be contained. The subject itself has been present in the software world for almost three decades and has been studied by professionals for a similar time. You can find more information about it in the few links below; They hardly exhaust the topic, but might be a great stepping stone to find out more:
- Technical debt: overview
- The technical debt quadrant
- The human cost of tech debt
- Technical debt and scrum
- Case study: locating the roots of technical debt
Want to find out if technical debt is impacting your software project? Contact our team and see what we can do for you!