Lately it has become popular to justify any messy code written in haste as intentional technical debt: “we chose to sacrifice quality for speed.” I’m not sure this is a constructive trend.
In my own work I’ve begun making a distinction between technical debt and technical waste. Here’s how I define the two:
Technical Debt consists of liabilities incurred from decisions based on insufficient knowledge.
Technical Waste consists of liabilities incurred through negligence or lack of discipline.
Technical debt is often unavoidable. You couldn’t be expected to realize that your micro-CMS system really should have been architected as a messaging system. Sometimes it’s avoidable given sufficient time for research, but you make the business decision that you can’t afford to take that time. For instance, you’re pretty sure MongoDB is a better fit for your domain. But you opt to use a traditional SQL RDBMS anyway because your team already knows SQL really well. You’re concerned with how long it will take them to become conversant in NoSQL patterns and strategies.
That’s Technical Debt: decisions to defer “known unknowns” until later in the development process. Technical Waste is the stuff you do when you know better. For instance, you’ve chosen to go with a better-understood data storage technology instead of the latest and greatest NoSQL solution. You know that the best way to give yourself options later in the process is to build some kind of clear dividing line in your codebase between a small set of database-specific objects and a larger set of storage-agnostic objects. You know that if you establish the line early enough, and establish clear guidelines and examples for how to maintain the division, it won’t significantly impact the project schedule. But through laziness, or distraction, or perhaps because you don’t want to hurt the feelings of team members who are producing working-but-highly-database-coupled-code, you allow the line to be blurred.
Technical Waste is often found in the nitty-gritty of code construction. It’s the test you blow off writing. It’s the 21st line of code you add to a method instead of refactoring it into smaller methods first. Technical Waste typically seems harmless in isolation; but multiply it by thousands of lines of code, and one day you realize you have a serious problem on your hands. Technical Waste is death by a thousand slips.
UPDATE: It’s important to note that Technical Waste isn’t just about the things you fail to do. It’s just as much about doing too much. Early in my career, like many young programmers I created a lot of technical debt by over-designing and over-coding systems that didn’t really warrant it. I call this Debt, because at the time I didn’t know any better; my head was full of UML and Patterns and not much experience. I haven’t completely kicked the habit of overthinking things; but I know that it’s waste when I do it, and I make an effort to keep the impulse in check.
Waste is often easy to justify in terms of a short-term time savings. But it consists of shirking practices which, when practiced diligently and with judicious use of tools, can have a net-zero or even net-positive impact on project velocity. Conversely, they are also practices which become more arduous and time-consuming the longer they are left undone: for instance, when you fail to lay the groundwork for a new method with a test first, the next, more complex test is that much harder to write from scratch.
From Debt to Waste
There isn’t a fixed line dividing technical debt and technical waste. The longer you practice your craft, the more you make good decisions an intuitive part of your process; the more you become both fast and good. Ideally, you should always be pushing the line back so that more and more decisions fall into the the realm of “waste” instead of “debt”. That may sound odd at first; but what I mean is that the more you practice, the more you become able to feel when you are lapsing into bad habits and creating waste. Remember, Technical Debt is about unknowns: questions you don’t have time to answer. Waste is about knowns: when you have a pretty good idea what the right choice is, but you rationalize a reason not to make it anyway.
To make that a little more concrete, using an example close to my heart: for years, I created Technical Debt in the form of programs that had no cohesive guiding principle to the way they handled failure. Every time I created a new exception class heirarchy, or decided to classify a particular case as “exception-worthy”, I felt like I was taking a shot in the dark.
Finally, I’d had enough of the nagging feeling that error/failure-handling was a “known unknown” in my design repertoire. So I submitted a talk proposal about handling failure in Ruby. When it was accepted, I was forced to make a concerted study of exceptions and failure handling.
Several talks and a book later, I feel like I finally have some clear philosophical guideposts when structuring a failure-handling strategy for an application or library. In my own coding, I have pushed failure to adequately address questions about error handling from the realm of “debt” into the realm of “waste”. I have a discipline to follow now; if I fail to follow it that’s just plain laxity on my part, not “taking on debt”.
Pairing is Caring
You don’t have to write a book just to expand your understanding of what is wasteful. Ways to improve your instincts and discipline as a programmer are myriad, and could easily take up a whole other article, talk, or even book. If I had to pick just one, though, it would probably be pair programming.
Pairing helps in two ways: first, it exposes you to how other people make coding decisions. Second, it keeps you honest; it’s a lot harder to fall into bad habits and cut corners when you know someone else is concurrently reviewing your code.
Like I said, I could probably list a dozen more ways. But this article is long enough as it is. What about you? What are your favorite ways to improve your discipline and level-up your software design decisions?