Dodging the Technical Debt Trap
Technical debt is one of the hardest concepts for clients to understand. But if you don’t fully appreciate or respect technical debt, it can bring your app and the business you’ve built around it crashing to the ground.
Think of a Jenga tower. To make it taller, you have two choices:
Pull blocks from the foundation
Create new blocks
It takes more time and budget to create new blocks. Growth is slower, but more stable. It will take you longer, but your tower can get way higher.
The QA team can write detailed and organized acceptance criteria.
Devs can write well-architected and organized code.
The team has time to write documentation and automate tests where appropriate.
These blocks are “complete” – they are stable and relatively self-maintaining. As the project grows, they hold their ground.
Pulling blocks from the foundation is faster and cheaper, but growth becomes more and more precarious. Eventually, the foundation becomes so riddled with holes that the product begins to wobble. You’ll have to pause growth to shore it up — create new blocks and carefully plug them in — or face total collapse down the road.
The code equivalent would be your team receiving feature designs that aren’t well-documented, and because everyone more or less understands what needs to be done, and because the client wants fast delivery, the feature gets built without complete documentation, smoke-tested*, and launched. You can go a fairly long way like this, and you can have happy clients, too.
The problem is that as time passes and software gets more complicated, even the sharpest team will forget specifications in the absence of documentation, and if they don’t have time to write automated tests and periodically run full system tests, it gets harder and harder to keep the system in integrity. You’ll start to notice bugs in production releases, which shouldn’t happen. Rather than patching those holes and moving on, the right thing to do is to budget time for documentation, system tests and upgrades, and refactoring as needed.
Not sexy. Not cheap. And definitely not an easy sell to clients.
This, my friends, is technical debt: borrowing from the future to pay for today by short-changing necessary work to meet short-term goals.
*Smoke tests are tests run on a new feature without re-testing the broader system it’s part of. To see why that’s so critical, check out this piece from our series on QA.
What does technical debt look like?
Founders and CEOs rarely understand the ROI of investing in “unseen” architectural improvements. They figure if the app looks good and works for the user today, what’s the problem?
The operative word here is today. By the time the product begins to falter on a user-facing front, the situation is far beyond a tipping point.
Producing new features starts to take longer because devs are less able to re-use existing components, and integrating the new feature is more difficult.
The product suffers from more bugs, root causes are more complicated to pinpoint, and fixes create more bugs. Devs end up spending more time fixing bugs than innovating on the product.
Data flowing through the app becomes corrupted and untrustworthy.
QA testing can’t cover an adequate spread of use cases.
The product becomes a security or compliance liability.
There are a million real-world parallels. You still have to change the oil in your car, even when it’s running fine. If you don’t, your engine will explode. If you don’t remove the dryer lint every once in a while, your house will catch on fire. As a matter of fact, all the examples I can think of end in literal flames.
When a collapse happens, everyone reacts like it’s an act of God. But that is so rarely the case. Far, far more often, catastrophe is the result of day-by-day neglect.
Why choose maintenance over damage control?
So, rewind back to our Jenga tower image. This time, let’s say you choose to create and add new blocks, rather than steal blocks from the foundation.
As your tower grows higher, you’ll have to reinforce that foundation along the way. New features translate to more tests that must be run and a larger codebase to maintain. In other words, the more work you put into the visible parts of your app, the more work the invisible parts will require.
You may ask, “If costs are escalating either way, why not just go the easy route and accept the technical debt?”
The technical debt you incur gains interest.
It’s not as simple as doing it now versus doing it later. Doing it later requires more work, money, and time. So often, projects get to a point where a complete rebuild is easier, faster, and more cost-effective (eventually) than to continue working with the existing code.
But it’s a big push up front, and a lot of founders just don’t have the stomach for that. So they keep going further into debt, while the investments they do make in their product yield diminishing returns.
Technical debt hinders the full potential of your product.
When the code base is a swamp, devs are limited in what they can do, because everything becomes a workaround. While incurring technical debt allows you to move faster at first, you’ll eventually paint yourself into a corner of being slower, less capable, and less agile than you need to be, not just to stay ahead, but to merely survive.
The crappy truth is that as your app scales, costs scale, too. You don’t really have a choice in that. But you can choose where to put your effort. Wouldn’t you rather grow from a place of strength?
Finding the balance
As a founder or entrepreneur or idea-haver, you’ll probably find a tenable balance of quality investments and technical debt that allows you to meet goals while maintaining a reliable product.
But that balance can be hard to find, and once you go too far into technical debt, it can be hard to get out. That’s what I want to save you from.
Reset your expectations on the pace of growth.
The choice is between building it right and building it fast. Investing in quality will reduce speed and increase per-feature budget short-term. That’s ok. Your product will always work better and will be cheaper in the long-term. If you set a breakneck pace from the jump, it’s hard to throttle back, especially as you gain investors who become accustomed to the faster, debt-incurring pace.
Build regular maintenance work into your plans.
Paying for maintenance work on an as-needed basis sets you up for failure. Together with your development partner, decide on a happy, sustainable allocation for testing, refactoring, documentation, code clean-up, and so forth.
Find a partner you trust.
Even if you are a technical person, you can’t act as both a technical lead on your product and steer its evolution as a business. It’s crucial to find a partner you trust, who cares about building your product right for the long run. The right partner will raise the right red flags at the right time and help make a plan that propels your project both as a product and as a business.
Your app lives and dies by its most invisible parts. Code quality, clean architecture, and solid QA embody the execution layer of your app project. It’s in your best interest to compromise on them as little as possible.