On Technical Debt

Maytee Chinavanichkit
5 min readFeb 25, 2019

--

It is kind of funny. These words, technical debt, shows up way too often in my life as a software engineer. And I am fairly sure I am on the fortunate side. My career has been around hip companies from the startup world to midsized companies to large multinational corporation. Regardless of the size of the codebase, size of the team size, size of the company size, taking it slow and building it right or move fast and break things, I still hear the same complaint. We need to clean up our technical debt.

Ward Cummingham invented the term technical debt back in 1992 to educate his peers in finance why refactoring is a necessity. Like taking out loans to accelerate a project forward, taking on technical debt by introducing shortcuts, rushed implementations, etc allows for faster delivery. Technical debt like all debt must be repaid. Instead of cash, technical debt must be repaid in refactoring. If ignored, technical debt will accrue interest. Over time, this can balloon and will derail future developments. Therefore refactoring is a necessity and this message is still relevant today.

However when I hear engineers talk about technical debt, I think they missed that message. Sure hacking things together is a way to get into technical debt but many other things also get lumped into this bucket. Ironically, there are also technical debt that no one really talks about. Can we still call it technical debt if it is not relevant? Let’s look at two examples.

Example 1) You too must have touched a legacy system at some point of your career. Today, you are asked to change how it works. The feature to implement will not play quite nicely with the existing code and its layers of abstractions. To make matters worse all the member who wrote this software quit months ago. You too would claim technical debt as why development will be slow. But upon further analysis, the system has been running in production for years. Given what it was suppose to accomplish, more often than not it is well rather well thought-out. Can you still call it a blob of technical debt?

Example 2) you too must have quickly hacked together something for a release. How long has that code been in production now? Maybe half a year. Maybe longer. Since there was no need to change it, nobody complained about it. It should be technical debt by definition. But somehow there isn’t any tangible interest in the sense that prevents further development. Can you still call this hack technical debt?

Expanding on Cummingham’s definition, I say technical debt is a measure of the gap between today’s code and today’s needs. The further the gap, the larger the technical debt. Think about it this way, all the code you will ever read was written for the needs of yesterday. From the number of users the software needs to support, to the features it needs to have, to the underlying data structure it uses, to how it deals with increasing growth, all these are assumptions baked into how the code on the day it was written. Once written, it becomes a relic of the past. Today, in the present, these needs change. That’s the default way the world works. Some the code will deal with this better while others will surely break. We get into technical debt not because engineers took shortcuts, rather the methodology taken are not flexible enough. Sometimes we get lucky and the hack can live on with zero code changes. Other times we have to fight it with a big refactor.

And that’s the most profound message I got from Cummingham. You need to refactor continuously because, by default, code will accrue technical debt. To achieve this, you needs two parts. One is to refactor as you go. Two, keep the code refactor friendly. If you have not read Stephen Covey’s 7 Habits of Highly Effective People, I recommend you do. Beyond effective people, production (P) and production capacity (PC) concept also extend to programming.

Refactoring as you go is straight forward. Break down big functions. Write test. Modify existing code with a version that better fits the needs. That should break the test. Now fix the test. Repeat. Repeat. And repeat. Start small and it will build up. That’s it.

Keeping code refactor friendly is more interesting and can be summarized in 3 words: leverage the computer. Let it help you code. Start by typing your code. Even if you write a dynamic language accept type hinting nowadays. For example Python’s mypy is far from perfect but having it will get you pretty far. Run code linters. The javascript tools like eslint are amazingly good at this job. Run tools like Java’s findbugs. The combination of these will help you catch and squash most stupid programming errors. Next, learn the IDE’s refactor tools. IDEs today are ridiculously smart. With the help from the previous step, the IDE can tell you in an instant all the instances of certain method signature. On top of that it can manipulate all of them in one go. That right there is your 10x refactor productivity boost.

Keeping refactoring friendly also bleeds into testing. Aim for attack vectors over reliance on one approach. From personal experience getting above around 60–70% coverage on unit test starts to erode productivity. And as a negative bonus it will still fail to catch issues. Simply because the code and the test are tightly coupled which is tangent to surfacing unknown-unknown failures. Attacking the problem from multiple angles. Write unit test, integration test, end-to-end test, QA test, release candidate test, etc. This disallows more issues from slipping through to production than the previous approach.

Patching bugs in production is the fastest way to kill refactor friendliness. This is a big no-no. Under pressure to get things working is where bad hacks are introduced. We need an exceptional case here. We need to hack in this field. We need to get it working now. We can clean it up later. This mindset kills code refactor friendliness. The code itself also fairs no better as it is no where near where it needs to be.

Again, technical debt is a measure of the gap between today’s code and today’s needs. Cummingham’s message was not about justifying hacking, rather to suggest that continuous refactor is a necessity. As inconsequential as some refactors may be, every refactor counts and keeps the code friendly ensures future refactors are also easy. There is one catch. Sometimes today’s need is completely incompatible with today’s code. Think, going from large bulk processing to realtime data streaming. Here is where I throw out refactoring and start rewriting. But remember to keep this new project refactor friendly and continuously refactor as you go.

--

--