Tech debt and engineering culture
Anyone who scratches beneath the very surface of western financial systems understands the extent to which well-managed debt plays an important role in economic growth and stability. We use debt to purchase assets like property and growth stocks, we finance vehicles to enable our families to support themselves, and at a macro-level, our governments use debt to fund infrastructure upgrades and social services. We are effectively purchasing increased leverage at a small premium over time (i.e. interest).
So why is it that so many engineers see debt of the technical variety as a dirty stain on a system left by an incompetent engineer that is to be avoided at all costs and eradicated as a matter of urgency?
The answer to this generally can be found in the culture of the broader engineering community. We are conditioned very early on in our careers to believe that our value is measured almost purely by the extent to which we understand and are able to implement technologies and systems of increasing complexity. Go to any technical forum or meetup and you'll see this in action - a group of engineers, especially those from larger startups and corporates, will almost always end up trying to out-alpha each other by discussing topics of increasing complexity and obscurity. Passionately discussing technical minutiae with seemingly no consideration for how, if at all, this materially improves the value delivered to end-users and assuming that engineering can or should be done with an unlimited budget.
Once you recognise this culture in action, it's not difficult to see why engineers are often so averse to technical debt - it is in contrast to the engineering cultural norms of prioritising technical purity over end-user value. We've all seen this in systems built on fully orchestrated Kubernetes-supported microservice architectures with a service mesh capable of canary deployments and layer 7 load balancing that has zero users or revenue.
What is tech debt?
Like all terms based on metaphors (financial markets in this example), the definition of "technical debt" is very much open to interpretation. It was originally coined by Ward Cunningham, however, the following is a generally accurate description in my opinion.
Technical debt refers to software design and implementation decisions that are optimised for speed of delivery over technical robustness.
It's important to remember here that not all tech debt is created equal (more on this in the next section), however, the strategic use of prudent, deliberate tech debt is akin to taking on strategic financial debt.
Both strategic technical and financial debt share the same core concepts in the form of the debt principal and interest:
Debt principal: That which has been borrowed and must be paid back. In technical debt, this is paid back in the form of refactoring.
Interest: the accrued premium owed on the debt. Interest in technical debt is the extent to which the debt slows down your ability to ship code as the codebase expands.
Unintentional, reckless tech debt is more akin to habitual credit card spending. Low leverage, high-interest debt that eventually cripples your ability to deliver and extract value.
Types of tech debt
Steve McConnell originally categorised tech debt into:
- Intentional: implemented consciously as a strategic tool
- Unintentional: implemented unintentionally as a result of doing a poor job
Martin Fowler went on to expand this into the "Tech Debt Quadrant" with two dimensions:
- Reckless vs Prudent
- Deliberate vs Inadvertent
Why the engineering community should embrace tech debt
There are several reasons why well-managed tech debt is a net positive for growing companies:
Tech debt creates leverage to move fast and create outsized value
Taking on well-considered, well-managed tech debt allows engineers to stay focussed on creating user value as quickly as possible. This is especially important in early-stage startups that are pre-PMF and/or pre-revenue.
Remember - your users don't care what your codebase looks like, they care what value your product provides.
Tech debt allows you to test your riskiest assumptions
When building new products or features, we do so based on some assumptions about the user and their needs. We don't want to be implementing perfectly crafted technology solutions when we aren't certain that they won't drastically change or even be thrown away.
Tech debt allows us to test these assumptions before putting further effort and resources into crafting a longer-term solution.
It takes time to identify the optimal implementation
"Do it right the first time" is a misnomer in systems engineering. You probably don't know what right is yet, and that's OK.
When designing and building novel systems, it is naive to think that you'll be able to identify the optimal design approach from day one. It may take many months and several revisions before you reach a level of understanding of the system to be confident in investing in longer-term system designs.
Tips for managing tech debt
Poorly managed financial debt can create serious problems, and tech debt is no different. If not managed well it can cripple your ability to quickly ship code, attract and retain great engineers, and even kill your company.
Here are some general tips for how you should approach and manage technical debt:
Optimise for "Quadrant II" tech debt (prudent and deliberate)
Tech debt as a strategic tool exists in the form of tech debt which was well considered and identified prior to implementation. This type of debt is inherently easier to manage and enables you to identify and control technical risk.
An example - a new feature within your application requires an RBAC authorisation framework. Instead of implementing OPA or Ory Keto, use a simple user/role association table. Once you've validated that the feature is important to your customers, then look at investing in a more scalable solution for authorisation.
Keep tech debt well-bounded
Identifying and managing the boundaries of technical debt is critical in maximising the upside and controlling risk.
Think of this as akin to interfaces in software engineering - define an interface that the rest of your codebase interacts with, so the technical implementation details of that module or sub-system can be refactored almost, if not completely, in isolation.
Gradually pay down your debts
It's important to make sure that your tech debt doesn't spiral out of control and become a net negative in your ability to ship code fast. Make sure you're budgeting time/resources for refactoring tech debt just like you would in the initial implementation.
At rebank we use the Shape Up methodology to build our product, which includes a 3 week "build" cycle, followed by a "cooldown" week where engineers are free to work on bug fixes, tooling optimisations and refactoring tech debt.
Generally, we ask engineers to think about "what slowed us down the most during the last build cycle?" and then work on optimising that for the next build cycle.
After several years of working in startups, I can confidently say that the best engineers I've ever worked with are not necessarily the ones with the deepest technical knowledge or fastest code writing abilities. The best engineers are those that operate at the product level as opposed to the systems level. They understand the user problem to be solved and they use their systems knowledge to solve them in the most efficient way possible. Your technical problems solving skills are useless if you don't understand the user problem you're solving.
The use of strategic technical debt is an important and powerful tool for delivering outsized user value and doing it quickly, and while it's contrarian within engineering circles to champion tech debt, it's a mandatory requirement if you wish to be considered an exceptional engineer.
Feel free to get in touch on Twitter or in the comments.