Andrei Pfeiffer logo
Back to Articles

Tech Debt or Tech Loan?

Software entropy
8 min read

If you're interested in Technical Debt, hats off to you because you probably care about the code you write and read. In case this term is new to you, Wikipedia offers a great overview of what Technical Debt is and what are its common causes:

"Technical Debt reflects the implied cost of additional rework caused by choosing an easy (but limited) solution now, instead of using a better approach that would take longer"

So, Technical Debt is a trade-off, a choice we make as developers. Sometimes we are (or feel) compelled to make these trade-offs. Sometimes it's our own decision. The reality is that we can't always avoid them, so we need to learn how to manage them.

While it's not easy to pin-point what pieces of code should be considered as Technical Debt, there's a general agreement that it's related to software design flaws.

Design flaws

Personally, I've seen 3 major levels of code when it comes to software design:

  • Well designed code
    It refers to flexible, tested, documented code, which is easy to read, understand, extend, modify, or remove. Smaller libraries can easily reach this level of code design, while large applications might contain only parts of such code.

  • Good enough code
    This is code that lacks some of the aspects of well-designed code, due to complex requirements. For instance, it could be flexible, but a bit difficult to understand. Or it could be very well tested and documented, but not easy to extend. This generally applies to bigger libraries and applications, which usually contain a considerable amount of decent, but not ideal code.

  • Poorly designed code
    This includes a wide variety of symptoms that you've probably encountered by now:

    • opaque code, which is difficult to read and understand;
    • rigid code, which requires a complete rewrite in order for its functionality to be extended;
    • fragile code, when tiny changes break unrelated parts of code;
    • no associated tests, which makes the code impossible to refactor, in order to fix the problems above;
    • scary code, which we don't touch, by fear of breaking something.

We should all aim towards writing well-designed code, even if we are only able to reach a good enough level. Needless to say, it's the poorly designed code that we should stay away from. It has a tendency to rot, which can potentially make our code unsalvageable.

Why do we write poorly designed code?

I'm pretty sure nobody writes "bad code" on purpose. There are various reasons why we do this, and next, we will cover the ones that are understandable, skipping negligence or not giving a damn.

Ignorance

During the first years of experience it can be very difficult, if not impossible, to identify any potential design flaws. We've all been through this phase, and will probably find ourselves there again and again, when dealing with new challenges.

We must learn how to identify poorly designed code, in order to (re)write it better:

  • Whenever we need to use a lot of words to explain a piece of code to somebody else, it could be a sign of opacity, so we need to make the code more readable.
  • If we need to do a lot of code changes in order to be able to implement a very simple feature, it's a sign of tight coupling. Writing loosely coupled code is a vast topic that we'll constantly have to become better at.
  • Bad code has a tendency to smell, so if we learn to identify these smells and learn their solutions, we'll know how to refactor our code accordingly.

This scenario doesn't really fall into the category of Technical Debt, as there's no actual trade-off. To talk about debt, we have to be aware of a better, more robust approach, but decide to choose a quick & dirty solution instead. If we don't know a better solution, we're basically implementing the right solution, even if it's a poor one.

Deprioritization

Through experience, we'll be able to understand the trade-offs, and we’ll probably feel the urge to fix them. We might even:

  • create an issue in our git repository;
  • create a task in our issue tracker;
  • mark it with a @todo comment;
  • write it down in our notebook or on a whiteboard.

However, none of the above helps if we don't actually fix the problem. Even worse, if the trade-off "works as it is", we might be forced to move on to other, more important tasks or features.

Eventually, this will lead to broken windows, which will incur Technical Debt. We might even believe that "we did our part", because we have acknowledged the problem, and now it's time for somebody else to prioritize the refactoring.

Acknowledging a problem, but not fixing it, doesn't actually solve the problem.

The technical nature of the debt

It's true that Technical Debt has a huge impact on the product as well, not only on its development. But we, as developers, need to understand that we are the ones that made that choice.

There's a reason why it's called Technical Debt. Even if the cause is not technical, the code which includes the Technical Debt is written by us, developers, and we are the only ones that are able to "pay the debt".

We need to take responsibility for the code that we write. We need to do whatever is necessary to fix the trade-offs that we know will become big problems later on, for the development team and consequently for the product itself.

Introducing Technical Loan

The problem with debt is not the trade-off itself, but the interest that we have to pay over and over. A loan, on the other hand, implies the same trade-off, but without any interest.

Think of it like borrowing money from a friend for a short period of time. We'll still need to return the money, but we won't pay any additional interest.

We can apply the same mindset to technical trade-offs: as soon as we are aware of a trade-off that could turn into technical debt, we must address it right away.

Discuss it

Bring it up for discussion during the next meeting. Explain the problem, and come up with solution/s. If we don't have a proper fix, we should ask our teammates for help.

Take initiative

Instead of saying "Let's do this", we should say "Let me do this".

Remember the last time you met an old friend on the street and you said "We should have a coffee", and your friend agreed, but neither of you took initiative?

Instead, you could have said, "We should have a coffee. How about next Monday?". I bet that would have turned out completely different.

Make it a priority

It's important not to start working on new features before fixing the outstanding trade-offs. Depending on the management, or the development process, there are various ways to approach this:

  • If we're dealing with a cerebral manager, we should be able to address the problems freely and include the refactoring in our next sprint.
  • If we're dealing with a pushy manager, we need to push back. We should always be sincere and firm, but polite.
  • If we're dealing with a non-technical person, we should avoid terms like "refactor" or "rewrite". Instead, we can tell them that "We need to make some changes to accommodate (the following) new features".
  • If we're unlucky and we're dealing with difficult clients or managers, we can include the refactoring in the new feature. As long as they don't know (and honestly don't even care), and new features are released as estimated, everybody's happy.
  • If they ask us "Why is it taking so long?", explain that "We had to take some shortcuts in order to meet the deadline, and now we need to clean up the code".
  • If all of the above fails, we can disguise the refactoring under some task that includes "security" or "performance", depending on what might tickle our manager or client.

But we need to keep a balance between refactoring the existing code, and releasing new features:

  • allocate 1-2 team members to focus on the refactoring, while the rest of the team develops new features;
  • if the entire team is required to focus on the refactoring, we can allocate only part of the sprint for it and the other part for new features.

Take loans. Avoid debts.

Taking shortcuts in software development is unavoidable. It's also a powerful tool to have around when needed. However, we must understand that any shortcut is a trade-off and all trade-offs have consequences.

Whenever we have to deal with debt we pay constant interest. The bigger the debt, the higher the interest. The more we avoid paying the debt, the more interest accumulates. The only way to get rid of the interest is to pay the debt.

We can avoid debt if we make a loan instead. No interest to pay, but we have to return the loan as soon as possible.