A bog. Difficult to traverse but an ecological necessity.
Technical debt is a commonly misunderstood term. It’s often put like this:
Technical debt is accrued when a feature is implemented the quick way rather than the right way.
Which is pretty fair and succinct. However, there isn’t a right way to do anything. It’s a case of balancing your needs and optimising accordingly. Maybe performance is your priority; or flexibility or a combination of those two or others. For example, in early stage startups you often don’t know if you will still be around next month, let alone if your business model is going to work. The optimisation is obvious — expedient delivery takes precedence over everything else.
In this case, our definition of technical debt is conflicted. For many early stage startups, the right way is the quick way. It’s also absurd to say that the typical startup Minimum Viable Product (MVP) has no technical debt. The trade off is that code optimised for productivity usually results in maintainability being sacrificed. This is really what engineers mean when they complain about technical debt — the code is hard to maintain. Lets refine our definition:
Technical debt is accrued when a feature is implemented in a manner in which maintainability is sacrificed in favour of other requirements.
This poses an interesting thought, suppose we are building a product that requires extraordinary performance — something real time, something handling lots of data, maybe financial. Clearly this requirement would be at the forefront of the engineering teams mind when designing and building the product. Code thats been highly optimised for performance is often difficult to read and understand — maintainability has been reduced. By our definition, technical debt has been accrued.
This is what dealing with technical debt is actually like.
Before considering ways to deal with technical debt it’s worth looking at why it’s bad and why engineers make such a fuss about it. Technical debt gets its name because it’s an analogy with real debt, which is accrued when something is borrowed. Our original definition chimes in nicely with this analogy. Doing something the quick way is like debt because you will have to pay it back later when you redo the feature the right way.
For our more precise definition things are not so clear. This is because the essence of the analogy lies in a different aspect of debt — interest. When a codebase has high levels of technical debt every time an engineer is working on it they waste time trying to understand code or work around poorly defined behaviours. This wasted time is an interest payment and this is where technical debt gets it’s name from.
With real debt interest rates are often fixed, well understood and applied on a regular basis. This enables individuals to plan around their debt and empowers them to borrow responsibly to buy a car, house or something else that they couldn’t realistically save for. Technical debtors lack this luxury. It can be very difficult to predict when an engineer will stumble on some nasty code and lose time dealing with it. There are ways of identifying bad code but even then it’s not easy to work out just how much time is going to be lost. This means that estimates become less reliable.
The analogy goes further though. Like real debt, technical debt can easily spiral out of control. If an engineer has 8 hours to implement a feature but spends 6 dealing with technical debt they are going to be tempted to prefer an expedient solution to a maintainable one which increases the level of technical debt still further.
A common pejorative term among software engineers is “big ball of mud” which refers to a code base that is riddled with technical debt. Hacking round technical debt is not much fun. Engineers want to build things and they don’t like being impeded or being wrong about their estimates. If it’s taking twice as long as it should to deliver functionality engineers don’t feel like they are making progress and become frustrated. Technical debt annoys engineers greatly and often they would rather find a new job than put up with it for an extended period of time. This can lead to a destructive churn cycle, where waves of engineers come into a team, estimate based on previous experience, hit the mud, have to hack in more messy code to meet the estimates, get fed up and then leave the solution in an even worse state to the previous people.
A debtors prison. I don’t know what the technical debt equivalent of this place is, but I don’t want to go there.
We’ve identified two situations in which technical debt is going to be hard to avoid — the scrappy startup with the shortcuts that allowed to live another day and the global bank with the obscurantist optimisations that gave it an edge. It’s not reasonable to say that there’s an approach to software engineering that will eliminate debt entirely so we have to find ways to deal with it.
If you have a minuscule amount of seed funding and need to get to market tomorrow then you have more pressing concerns than technical debt. Don’t worry about it for now, just keep bashing the keyboard.
However, it’s important to understand that code created without any concern for maintainability is disposable. It’s never going to be suitable for production in even the medium term. What you are building is a sketch not a painting and is effectively a form of sacrificial architecture. It is essential that stakeholders understand this although they will inevitably be tempted to get as much out of their investment as possible. There is no getting away from the fact that later (although probably sooner than you might expect) on you are going to have to rebuild. Plan for it.
There are some kinds of technical debt that are completely avoidable regardless of circumstances. These tend to be code hygiene related things like commented out code. Be strict about this; use linters and other tools to enforce standards. Code quickly but code cleanly.
Isolating messy code is a very effective strategy for dealing with unavoidable debts. It’s particularly useful for when some parts of a solution have to be extremely performant. With clear and well defined boundaries engineers can treat it like a black box that’s full of dragons. Any features that require changes to the dragon box will be known to be risky and estimates can be adjusted accordingly.
This touches on a more general point. All well designed software is composed of small modules with clear boundaries and limited responsibilities. As such well designed software is more tolerant of messy internal implementations as they can be replaced with limited side effects to the wider system.
Spending time on getting the architecture of a system right enables you to save time writing disposable implementations that can be scrapped and replaced when the need arrises. Think of good architecture as having a great credit rating — you can borrow more without getting into difficulty.
This slice of suburban paradise was hopefully bought with the help of an affordable mortgage. Not all debt is bad.
While technical debt is derided by engineers as the root of all evil, much like real debt, in practical terms it’s difficult to avoid. Having a clear understanding of what sort of technical debt you are going to run up will enable you to have a solid strategy for dealing with it. Hopefully that’ll mean you will stay on top of it and write better software with happier engineers.