A Heaping Helping of Technical Debt
I was talking to a founder about a process for working down their backlog of technical debt when it dawned on me that many business owners might not actually understand the concept at all.
Consider your car. Most cars require you to get the oil changed every 3000 miles or so. Once you get past 3000 miles you start running the risk of having problems. Maybe you won’t see a problem right away, but somewhere down the line you might end up with a mess. The longer that you wait to change the oil, the greater the risk becomes and the more near-term it gets.
Technical debt is nothing more than required maintenance of a piece of software, like changing the oil in your car. The difference is that the technical debt is coming from three directions:
1. External Influence — Your technology partners and suppliers. Every time that they make a change to their software, they are adding technical debt to your software. When Oracle releases version 12 of their database software, you inherit the technical debt of updating your database from version 11. You don’t have to do it right away, but you are going to have to do it sooner or later. The longer that you wait, the more difficult it will be. If you wait until Oracle ships version 13, you could end up in big mess. We’ll talk about this kind of technical debt in more detail in a subsequent post.
2. “Fashion” — Trends come and go in the technology business.
For example, at one point technologists had the idea that putting all of your business logic into your database was the smart thing to do. A few years later, they changed their mind. As technology trends move out of fashion you, the business, are stuck with the task of removing these older designs out of your code line.
3. Internal Influence — Your own development processes and the changing business requirements of your software.
For the remainder of this post we are going to talk about just this sort of technical debt – let’s call this internal technical debt.
Internal technical debt is a backlog of software maintenance that you have built up all on your own. Executive management, product management and software engineers all contribute to the problem. This probably sounds confusing to you. After all, how can you build up your own technical debt? It’s easier than you think. To show you how, let’s use a fictitious, but realistic, example of business logic.
Let’s say that you have a web ecommerce platform with an electronic shopping cart that your customers use to buy your product. Early versions of this software allowed users to add products to their cart and make purchases by entering a credit card. Simple.
At some point, somebody in the company gets the clever idea to add a discount code for the upcoming holiday season to drive a little extra business by encouraging your customers to speed up their purchases. Sounds like a great idea. At this point your team may not have a fully fleshed out idea of what the “discount code” should do. However, since you had embraced a “ship and iterate” mentality, you might pull a basic storyboard together and dive in.
[Now clearly I am being a little tongue in cheek. It’s never this bad, but I am simplifying the process a bit in order to tease out the core of the problem. So I am asking for a little liberty here.]
Let’s say that you call this new feature DiscountCode, and your team agrees on a basic set of features:
– A discount code field will get added to the checkout screen
– The discount code itself is a string called “HOLIDAY2014″
– The server-side code will look for HOLIDAY2014 and subtract 10% from the order
Engineering starts cranking out the code and the test cases.For the purposes of our little tale, we’ll ignore the client-side code and just concentrate on the server code.Engineering adds the following code to the checkout layer:
/* ** ... Checkout code */IF (@V_DISCOUNTCODE == "HOLIDAY2014") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); }
Simple, really. If the variable @V_DISCOUNTCODE is “HOLIDAY2014″ then we take 10% of the @V_TOTAL_SALE. [Once again apologies for this psuedo-code. I’m going for simplicity here, not precision.] So far, so good. This is a pretty simple change and, while it’s maybe not that well done, we haven’t really disrupted the applecart so far.
Now let’s say you realize that you shouldn’t be enabling the discount code right away — after all, this is supposed to be a holiday promotion. So you make a simple change:
/* ** ... Checkout code */ IF (@V_DISCOUNTCODE == "HOLIDAY2014" and @SYSDATE BETWEEN "2014-11-28" AND "2014-12-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); }
Still okay, we dodged a bullet there. Let’s say that our little promotion worked pretty well. So we decide to add a first-quarter promotion in order to spark up the post-holiday sales. We’ll make this one a little more aggressive because the first quarter is usually soft.
/* ** ... Checkout code */ IF (@V_DISCOUNTCODE == "HOLIDAY2014" and @SYSDATE BETWEEN "2014-11-28" AND "2014-12-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } ELSEIF (@V_DISCOUNTCODE == "1STQTR2015" and @SYSDATE BETWEEN "2015-01-01" AND "2015-03-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); }
We’ve got to keep the old code in there, even though it’s not valid, because we might need to go back in time to understand why holiday sales in 2014 had a discount.
[Once again, a small aside in the hopes of avoiding a flame-war here. Yes, most developers wouldn’t be this sloppy. They would log the data. They could update the source and remove the old code, knowing that their version control system would track the changes.]
By the end of the year we’ve ended up with several more simplistic promotions as follows:
/* ** ... Checkout code */IF (@V_DISCOUNTCODE == "HOLIDAY2014" and @SYSDATE BETWEEN "2014-11-28" AND "2014-12-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } ELSEIF (@V_DISCOUNTCODE == "1STQTR2015" and @SYSDATE BETWEEN "2015-01-01" AND "2015-03-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); } ELSEIF (@V_DISCOUNTCODE == "2NDQTR2015" and @SYSDATE BETWEEN "2015-04-01" AND "2015-06-30") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); }ELSEIF (@V_DISCOUNTCODE == "3RDQTR2015" and @SYSDATE BETWEEN "2015-07-01" AND "2015-09-30") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .25); }
Sales decides to get cute in the fourth quarter and add several different holiday discount codes in order to REALLY spice up sales. For the day AFTER Thanksgiving they get a 15% discount, on Cyber Monday they get 25% and then after that the discount code drops to 10% until the end of the year.
/* ** ... Checkout code */ IF (@V_DISCOUNTCODE == "HOLIDAY2014" and @SYSDATE BETWEEN "2014-11-28"AND "2014-12-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } ELSEIF (@V_DISCOUNTCODE == "1STQTR2015" and @SYSDATE BETWEEN "2015-01-01" AND "2015-03-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); } ELSEIF (@V_DISCOUNTCODE == "2NDQTR2015" and @SYSDATE BETWEEN "2015-04-01" AND "2015-06-30") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } ELSEIF (@V_DISCOUNTCODE == "HOLIDAY2015") IF @SYSDATE is "2015-11-27" { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); } ELSEIF @SYSDATE is "2015-11-31" { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .25); } ELSE { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } }
By the first quarter, you might decide to add discount codes for certain products, or only for certain classes of customers. This is when things start to get really hairy:
/* ** ... Checkout code */ IF (@V_DISCOUNTCODE == "HOLIDAY2014" and @SYSDATE BETWEEN "2014-11-28" AND "2014-12-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } ELSEIF (@V_DISCOUNTCODE == "1STQTR2015" and @SYSDATE BETWEEN "2015-01-01" AND "2015-03-31") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); } ELSEIF (@V_DISCOUNTCODE == "2NDQTR2015" and @SYSDATE BETWEEN "2015-04-01" AND "2015-06-30") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } ELSEIF (@V_DISCOUNTCODE == "HOLIDAY2015") IF @SYSDATE is "2015-11-27" { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); ELSEIF @SYSDATE is "2015-11-31" { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .25); } ELSE { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .10); } } ELSEIF (@V_DISCOUNTCODE = "1STQTR2016") AND (@SYSDATE BETWEEN "2015-01-01" AND "2015-03-31") IF (@V_CUSTOMERCLASS IN "1,3,12") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .15); } ELSEIF (@V_CUSTOMERCLASS IN "5,6,18") { @V_TOTAL_SALE = @V_TOTAL_SALE - (@V_TOTAL_SALE * .17); } }
While I could carry this problem out to its bitter end, let’s stop right here. I’m already cross-eyed anyway.
You can see from the preceding example that the code still works, but it is going to get harder and harder to debug and maintain. Somewhere along the way your development team is going to tell you that they should probably build out some database tables to hold discount codes, effective dates and values — and maybe some subtables for customer class codes and product class codes. They’ll also need some cycles to build out some user-interface panels for sales to use when entering and updating codes.
The important thing to note here is that a very simple requirement, providing discount codes, has snowballed into a much more complex beast. And while your real development team isn’t likely to try to implement a discount code in the imbecilic manner that I used here, the reality is probably closer than you think. Especially in the middle market. Your understaffed development team probably scrambles to implement a lot of “specials” on an ongoing basis. Over time all of these little hacks and sub-optimized code processes build up on themselves. Eventually, you are going to need to fix it. And that, friends, is the nature of technical debt.
Almost every major component of every system that you write is going to end up generating some self-imposed technical debt. And at some point you are going to have to pay that piper. It’s easier to do it a little at time, chipping away at the backlog of technical debt with each release cycle. When you fail to do so you almost always end up in the dreaded “re-write” penalty box. Complete re-writes are generally a mess of galactic proportions and you’ll be MUCH happier if you choose to avoid them altogether.