In software development, technical debt is almost always present. It’s usually hard to quantify, requires special coding techniques in order to be paid off and is often tricky to explain or show to a non-developer in a team or to management/stakeholders. Technical debt and the software entropy it brings lie beneath a lot of software making failures.
My role as a client and team lead is to communicate technical debt’s existence and its implications to the stakeholders or product owners in order to help them manage it. Thus allowing the decision-makers to achieve business goals and let their organisation flourish.
I start such a conversation by making sure that everybody is on the same page when it comes to what technical debt is and where does it come from.
Table of Contents
What exactly is technical debt?
The management, stakeholders, the product owner - these people drive the decisions on where to put focus on when it comes to using developer resources. May it be new features, promised on the new marketing campaign, or just keeping the web application product up to date with the ever-changing business domain surroundings. The priorities may vary.
Yet what is common for a well-operating business, the priorities and goals have to be set straight, however it is often the case that the decision-makers are often the people who understand the technical debt the least.
Let’s start with the definition I found on Wikipedia and see if it comes handy: “*Technical debt (also known as design debt or code debt, but can be also related to other technical endeavors) is a concept in software development that reflects the implied cost of additional rework caused by choosing an easy (limited) solution now instead of using a better approach that would take longer.”*
Of course, there is more to it in the original article, several sections cover various aspects of the technical debt and the resulting cruft in software development. The definition is pretty decent as it highlights the cost, the need for rework and time limitations.
Though it is a bit stiff, technical mumbo-jumbo rich, and not so useful when it comes to reimagining how the everyday development routine looks like, it shows how choices made during a software development iteration, like a sprint, translate to the creation of technical debt. Which in turn, results in software entropy in the form of a new, yet legacy code.
My intention isn’t to provide a more accurate definition than Wikipedia, but to cater one for people who do not necessarily appreciate the amount of technical jargon used for conceiving the definition altogether
Hence I do not start the conversion with “technical debt is a concept reflecting the implied cost of...” as it is far more useful to define it as a growth factor that makes every new requirement or change harder (and more costly) to implement (if not kept in check) to the point where shipping that new change is just impossible - an idea easily demonstrated with a line chart.
Also, it is far more useful to talk about various project development and maybe “life in general” situations, both a developer and a non-developer person can relate to, to show how these various choices impact our operation capabilities short and long term.
Where does technical debt come from?
The technical debt origins are usually of three kinds: the unavoidable, the accidental and the deliberate. Hence we talk about types of technical debt, with an important note though that they usually come up with different mixes and flavors.
Unavoidable technical debt
One of the very basic laws of nature is that things left to themselves, without proper attention and care - decay. This is true for many aspects of our life: relationships and love, items and our belongings and… software. A truth that I found is much easier to see and comprehend outside the domain of software systems.
Because, let’s imagine something typical: you’ve just built up your web application. What it means is there are two elements in place: the work from the MVP version of the web application including the code behind your business logic as a first element and the code and setup behind the infrastructure, required by your web app in order to operate, as a second.
And now that the web app is up and running you direct the focus on the business part, finally being able to iterate over the features and functionalities you had in mind, leaving the infrastructure part behind. Because why focus on something that’s already done, right?
When we think about cars we use for our everyday commute, it is normal for us to think or accept the fact that once in a while we need to park that car at the mechanic workshop because we feel that things aren’t working the same as they used to (“what’s that strange sound coming from the engine?”, “my brakes started squeaking, better check that out” and so on) or because they simply break. There is also a law in place in many countries requiring you to put your car through special maintenance procedures periodically.
Things break because there is a natural process of the item's element wearing off during use, but also things can break if we forget about the proper maintenance, like changing oil…
Now, you may wonder why I came up with that elaborate metaphor with cars.
Your web application is like a car. There are 2000$ cars and 2000000$ cars. What they share in common, is that they need maintenance. In the same manner, your web application requires a DevOps engineer’s attention, from time to time.
Using a car metaphor, a car would break from rust. In the web application world the app may become non-deployable because the code is “too old” (it uses libraries that are no longer supported on the operating system level or by particular infrastructure elements - often the case in cloud solutions) or the infrastructure can no longer support the web application code (the new and up to date web app libraries require the infrastructure and system counterparts that are not present in the old and legacy infrastructure).
Accidental Technical Debt
It’s often the case that web applications evolve greatly from their original and initial MVP form. The functional requirements change, however the design sometimes struggles to follow, despite even the best efforts made by the team of programmers.
Once again let's use our imagination. The case is as follows: in order to solve some business problem for a user, you need to ask that user for a pretty big chunk of data. Following some good UI/UX practices you avoid a pitfall of achieving so with one, big form on the page, that requires a lot of scrolling through in order to get to the submit button. The decision is made to organize and shape such a big form into a five-step wizard. The development team takes such a requirement and designs a nice solution where code structure follows the aforementioned, arbitrary choice of gathering user input in that certain way.
Yet, for whatever reason the original design doesn’t meet the user’s needs, so the idea is iterated upon and after several iterations of changes to the wizard flow you end up with a wizard which has three steps, including one optional.
A natural choice was to build on top of existing wizard functionality, instead of starting from scratch. After all, there is already written code that could be potentially re-used right?
And this is true in a lot of cases. However, what is easily overlooked here, is that we’re trying to reuse the code which was designed for a different flow.
And it is inevitable that as you shape the old design into a new one, the old, legacy design choices will affect the subsequent one, manifesting themself as various quirks to polish or to workaround. The things that just wouldn’t be there if you would just start designing the final solution or at least started further down the road here. The things that slow the programmer down. The accidental, outdated design technical debt.
But why do we all once in a while hear from a product owner a question: “well, you have all the pieces coded right? It’s just about gluing it altogether differently. Should be simple, isn’t it?”. Now you know why it’s not that simple, as you designed your original code with something different in your mind.
The accidental technical debt origin points to the utter importance of doing proper “homework” when it comes to prototyping and coming up with the solution together with the target user base. Plus the need for the cooldown (refactoring) phase is rehearsed after each iteration of an ever-evolving (possibly successful) web application.
Deliberate Technical Debt
In “The Clean Code” book Robert C. Martin introduces his reader to various situations he encountered in his career as a developer or a leader of a team of developers. He often chooses a literary form of a dialogue between hypothetical people, like a conversation between a developer and a project manager.
I found it amusing that one of the typical scenarios he has chosen several times was a story where somebody from management (i.e non-technical person) promised something in terms of software delivery (to the group of stakeholders or marketing department or just client base) and the trouble arose when a developer person in a dialogue informed his peer that “it cannot be done in a given time schedule”.
In these dialogue-stories a management person urges the developer to make a commitment to deliver the software feature in a shorter amount of time than anticipated by that developer. Just as if the provided delivery estimation was a kind of whimsy thing from a developer or a lack of goodwill in terms of achieving some company goals.
The developer in these dialogues is usually presented as a person who is doing good craftsmanship and high-quality code, but also a person of his word, who dislikes making promises and commitments that cannot be met.
The book author focuses mostly on developer assertiveness (giving tips about that to the reader), with some additions like elements of scope negotiation, but what I was missing during the lecture was people talking about what it really means to deliver something in one week when the estimation is two (weeks).
Developer’s strain and poor happix are one thing, but we’ll put that aside. What I would really like to see in such a conversation is a clear message, that the next time estimation for another would be either higher or less accurate and reliable, because of the technical debt incurred.
And that reluctance of a developer when it comes to pushing the deadlines (and making shortcuts) is in the best interest of a running business and its long-term goals.
A company or an organisation can stay competitive vs other market players only if it manages to keep the deliberate technical debt in check. The deliberate technical debt is easiest to manage out of the three types of debts, as you usually precisely know when you take it: by requesting it or by imposing it on your developers.
Generally speaking, we can say technical debt describes what happens when a development team expedites the delivery of functionality or software which later needs to be refactored. In other words, it’s a result of prioritizing speedy delivery over high-quality code.
We distinguish three types of technical debt: unavoidable, accidental and deliberate and each of them is caused by different circumstances, therefore, can be avoided or limited by taking different actions.
Being on the same page when it comes to what technical debt is and where it comes from enables us to discuss the approach we can take in order to stay on the right side of the debt when building and maintaining the software systems like a web application with accompanying infrastructure is.
Now that we have covered the most important aspects of technical debt and the role of proper communication when it comes to that phenomenon, we can move on to covering various techniques I found decent and working when it comes to dealing with technical debt. My next article will take you through those battle-tested methods, so stay tuned if that sounds interesting to you.