Everything is Technical Debt
On the heels of a post from Frank van den Brink that places a strict boundary on what technical debt is, and which uses other words and phrases such as negligence, and lack of professionalism to define practices within the engineering community, I’d like to propose a completely different approach to defining technical debt.
All code is technical debt. From the moment you move from the whiteboard or spec document into code, you are creating technical debt. Every single choice you make due to time constraints, engineering knowledge and proficiency, application requirements, and other factors makes it that much harder to implement changes in the future. You may now have to unwind everything that you have done in the past in order to accommodate the changes you’re about to make. This may be as complex as having to migrate your entire datastore or as simple as a configuration change and swapping out for a different third party library. In both cases, a change has been made and time was spent considering solutions to the problem.
Technical debt has a direct correlation to how many expletives you yell when making any changes to an existing codebase. This debt also includes the amount of time spent whiteboarding, the lost opportunity of working on other projects, and the number of nights you’re up late implementing new code that should have been much easier if you had made a wiser decision at the beginning. Because all decisions result in some level of technical debt, making the best decisions for your product — given its many constraints — will help minimize your debt and keep your team happy.
There are two types of technical debt: planned and unplanned.
When the engineering process begins, the tools available are immediately evaluated to ensure that a product that meets all of the requirements can be built. Not only is each technology scrutinized, but rarely does one technology hold the magic bullet that will solve all of your problems. For example, by selecting PHP, you may be leaving some asynchronous goodies on the table from Node.js. By selecting any RDBMS, you’re foregoing some benefits that any NoSQL engine will provide. Sure, you may want to use all of these technologies, but doing so will double your engineering time because you have to implement how all of these services share data, and you’ve also increased the Ops effort for maintaining the servers. Assuming that you have pressure to release the product sometime before the heat death of the universe, you start sacrificing features and abilities in order to deliver a minimally viable product. At Aol Alpha, we do this all the time. It doesn’t mean that the product or code is bad, or even that our engineers or management are bad; we’d much rather get a product out the door to test viability in the marketplace before spending gobs of money on a product that may not have any chance of success.
There are also other types of debt that are completely out of control of the engineer. Many people work on teams in both small and large companies. Usually, this means that other people design and control product direction. I’m sure these engineers can relate to the adage, “the only constant is change.” Let’s assume you have engineered — to the letter — everything in your original specification with as much abstraction, decoupling, normalization, and all of the best practices that you can manage. Your product manager then decides to pivot to a similar but completely unexpected product. Your entire codebase may now be a pit of technical debt through which you will need to wade to efficiently and effectively change in order to meet the needs of the company. Despite your best intentions and seemingly correct decisions, with a small change in context, your codebase now unintentionally contains technical debt.
As with planned technical debt, the best way to prepare for this is to ensure that your code is as modular as possible so that you can pivot as fast as your company needs.
Inexperience is not negligence
Next time when you see someone cutting corners and calling it technical debt, make sure you remember it by it’s real name. Negligence.
It’s rarely done deliberately, though. Often it is caused by a lack of insight or experience. Once people know what it is, and are willing to fight for the quality of their work, they are less likely to fall for it.
I don’t agree with this statement at all — at least not as an overarching principle. Many teams have different levels of engineers working together as a cohesive group. I don’t expect that mid-level engineers would be writing their code at the same level as chief architects considering (as a rule) they have less insight and experience. Does that make mid-level engineers negligent? Most certainly not. However, it’s up to the team, whether through code reviews, pair programming, or whatever training devices are available to educate and expand a less senior engineer’s knowledge. I’m constantly learning new things, and the code that I write tomorrow will undoubtedly contain more insight and experience than what I write today. The team should help point out potential areas of technical debt and work together to find a solution that fits all engineering and company requirements.
Managing your debt
Just because you’ve made some sacrifices doesn’t mean that you have to continue to build your technical debt by writing shitty code. Use established design patterns to decouple components. Do everything in your power so that if you do need to make changes in the future, it’s as easy as possible to do so. Perhaps this just comes down to thorough documentation. Maybe this means taking more time to write an abstraction layer so you can swap out SMS service providers. Anything you can do to assist future changes to the codebase will help pay down the technical debt you are building.
If you approach all programming with the understanding that you are creating technical debt with every keystroke, you can start paying attention to the integration points that will cause the most trouble. With experience, you learn the technologies that will best suit the needs of the product, as well as the areas in the code that may be most susceptible to needing refactoring. Before you know it, you’ll end up writing some great low-interest, high-yield code that should serve you well.
Thanks to Frank van den Brink for his opinion on this subject, and for inspiring this post. If you haven’t read his post already (I don’t know why you haven’t, considering I’ve linked to it a whole bunch of times, but alright, you do you), go ahead and check it out. It also contains other great links on the subject.