I think it could make more sense if it's rephrased to 'all code is a liability'.
Then the equation is simpler. More code = more to maintain = more liability. It says nothing about the quality of the code, just the quantity.
Tech debt is a trade-off between near term and long term ambitions - it is literally impossible to build a successful project without accruing this kind of debt, simply because you cannot perfectly anticipate the future. In that sense, it's not so much debt as it is a maintenance overhead.
Agreed, or similarly: all software has a carrying (maintenance, but I think that has connotations of not including full ongoing TCO) cost. Code is a liability in the sense that it has a non-zero carrying cost.
The ideal software implementing a given functionality has as low of a carrying cost as possible. More code, in quantity and complexity, typically means more carrying costs.
One of the appeals of SAAS is that this carrying cost is easy to quantify and predict. Going further, for high-quality OSS projects like the Linux Kernel, the carrying cost may be 0 or close to 0 for most users. But lower quality OSS or buggy SAAS has a carrying cost in the form of bugs (possibly affecting downstream users), patches, need for support + back and forth.
In this analogy tech debt is a tradeoff for some short term benefit (like simplicity, time to market) at the expense of higher carrying costs down the line. But some carrying costs simply can’t be reduced - you can’t mooch off something like the Linux kernel, get your own carrying costs all the way to 0, or find a SAAS provider/vendor willing to give you the software for free. So in this model not all code is technical debt.
> Code is a liability in the sense that it has a non-zero carrying cost.
The under-appreciated corollary to that: tests are code, hence tests have non-zero carrying cost as well.
> The ideal software implementing a given functionality has as low of a carrying cost as possible. More code, in quantity and complexity, typically means more carrying costs.
You understress the complexity point. Higher quantity of code with high clarity and low complexity would have lower carrying cost. This is where DRY bites people: ill-suited or excessive abstractions can be worse than a little bit of copy-paste that are immediately visible to the eye.
> In this analogy tech debt is a tradeoff for some short term benefit (like simplicity, time to market) at the expense of higher carrying costs down the line.
In a general sense, doesn’t this hold for everything we do? If you really dig into it, is there an activity that is not a trade-off of short-term benefit for higher costs down the line (be it waste, pollution, something unknown or generally entropy)?
Tests are indeed not free. But neither is having to spend two days to debug a weird bug you would have cought if you had them. Depending on the worst thing your software could be doing if it had a edge case mistake, writing tests is also just another take on trying to prevent them when they happen.
Also: nothing prevents us from deleting tests and do as if they never existed, except the nagging voice of sunken cost.
If I've learned anything over my career, it's that tests are treated as write-only code and they forever accumulate. Not only are they a burden in terms of comprehension, they have a measurable cost as your CI/CD pipeline becomes ever slower or requires more parallelism.
I've worked in so many places where a test suite suddenly takes a day to run with no parallelism, and requires 50 other machines to get it down to 20 minutes. And so many of the tests that waste so much time are down to them being pointless, or being stateful for no reason.
I constantly advise people to stop writing obvious bullshit tests (e.g. the kind of test in Rails where you just assert that you wrote `belongs_to :some_model` in your model), or ones that are too heavily mocked. Focus on your business logic and the inputs and intended outputs, not how you used a framework or dependency.
Not all code has equivalent carrying costs. I think the ideal behind tests is that they reduce maintenance costs of the code they cover by more than they add themselves (ideally).
"My point today is that, if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent": the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger."
"Programming systems can, of course, be built without plan and without knowledge, let alone understanding, of the deep structural issues involved, just as houses, cities, systems of dams, and national economic policies can be similarly hacked together. As a system so constructed begins to get large, however, it also becomes increasingly unstable. When one of its subsystems fails in an unanticipated way, it may be patched until the manifest trouble disappears. But since there is no general theory of the whole system, the system itself can be only a more or less chaotic aggregate of subsystems whose influence on one another’s behavior is discoverable only piecemeal and by experiment. The hacker spends part of his time at the console piling new subsystems onto the structure he has already built—he calls them “new features”—and the rest of his time in attempts to account for the way in which substructures already in place misbehave. That is what he and the computer converse about."
'All code is a liability' is also not a particularly accurate application of finance jargon.
A car or a house also have ongoing maintenance costs, but they would normally be classified as assets (because they generally produce value in excess of the maintenance, or because you can sell them in a secondary market).
I think an accurate, useful, but not very catchy rephrasing is just 'working code requires ongoing maintenance'.
I don't love this either because working code needs no maintenance whatsoever. After all it's already working. There's also no notion of "repairing" code because it isn't something that can just break suddenly.
So really what we're talking about is that necessary changes to the codebase can arise from not just the stakeholders as we typically define them but also because the environment around the code changes.
Someone who invested in shutters that can close instead of being purely decorative will be able to adapt more quickly and cheaply to a wind storm than someone who has to go out and buy wood to board them up.
Changes to the platform or dependencies break working code. This is increasingly hard to ignore when app stores stop vending your product to older versions of a platform, even if it's working fine.
And working code, no matter how well it works, imposes a cost to implementing new features. A new feature might be a cinch to implement in a new app. But it can easily take much longer to implement the same feature in an existing app.
> Changes to the platform or dependencies break working code
Not all code. You have platform dependent code and then code that doesn't depend on anything outside of your own code. The code with no outside dependencies can easily stay around unchanged for decades and is not a liability.
"All dependencies are liabilities" I could agree with. Dependencies are technical debt that will continue to demand interest from you, they are often worth it but they are the main source of maintenance burden. Some are unavoidable if you need to interface with an API, but many are not.
Unless you're deploying your code bare-metal on a machine with no OS, you've got software dependencies, and even in that situation you've got hardware dependencies. I think the idea of dependency-free code is a myth that we like to use based on the abstraction layers we're comfortable with - if I'm okay assuming that a given layer always Just Works™, then it magically stops being a dependency and becomes the new ground level.
That said, I agree that it's helpful to think of dependencies in terms of their potential liability as well as their benefit.
I use the term liability because the only code that has zero maintenance cost is code that doesn't exist.
If you have a viable solution to a problem that doesn't require code, then you still get the value from that solution without the burden of maintenance.
Classic case in point: your in-house infra platform on K8S is an incredible liability no matter how much value it adds, because it's a means to an end for scaling and serving your product. Or a cost-centre, in other terms. You could just as well host on a collection of SaaS and offload a large portion of the maintenance cost onto the provider.
Rather than writing code that is easy to maintain (read: easy to keep), it's optimal to write code that is easy to discard. The latter is a lot more challenging than piling on architecture and in some sense it's akin to having illiquid code (stuff you can't get rid of very easily), and liquid code (stuff you can get rid of easily).
I'm objecting to the use of the term 'liability' because in accounting/finance in particular, a liability is strongly implied to be a net negative once you weigh all the benefits and costs together.
Here you're saying code is a liability because it has costs. But a car or a house have costs, yet they're almost always classified as assets, because the benefits outweigh the costs.
So you're left with a few choices:
1. Accept that code isn't always a liability, at least according to the dictionary definition of the word
2. Argue your way somehow to code nearly always being a net cost (seems unlikely, otherwise why write code?).
3. Redefine the meaning liability for this particular application
‘All code is a liability’ some code is productive.
Different domains have different jargon. Code doesn’t map very well to finance because the loan is also the productive asset. It’s easy for a deprecated function to exist which literally nothing calls and therefore it provides you zero benefits, but such cruft is still an ongoing cost. Meanwhile that exact same function was critical last week.
I agree with the general sentiment but would generalize to "all corporate surface area is a liability" and that liability can be minimized in many ways.
An illustrative example where more code is a lesser liability: I work on a product with an activity feed feature built in 2018-19. There was a political battle on whether to build it internally or use a third party "feed as a service" product. The build it internally folks won. The "FaaS" under consideration went under during the pandemic, so we dodged that bullet. We also have customized it in ways not possible with an off the shelf product, with no big rewrite necessary.
On the flipside, we've never had time to implement things that are trivial with commercial FaaS like iOS and Android push notifications. Trade-offs are hard!
I don’t like analogies to concepts from economics for this reason; it seems like they are often more complicated than the programming thing that is being described, and economics is a whole ‘nother specialty.
The code to produce those runnable programs is a liability.
It's a necessary liability for a sustainable software business, as without the code fixing bugs is nigh-impossible (though not theoretically impossible - long-ago programmers wrote and read raw machine code).
But, the code itself is not what produces value. That's the code's output.
(Yes, you could be clever and say "but what about interpreted languages?"
I'd respond by pointing out that it's pretty rare to distribute a pile of executable code files to end users. Front-end JS only producing UX if delivered as part of a website, and similar principles hold for other interpreted languages - they're effectively object files once deployed, as in any deployment they're being run by the interpreter, not read by a human.)
> The code to produce those runnable programs is a liability.
That is total nonsense. So you throw away all your code after you built the program binary to avoid the liability? Don't think so, that code is a massive asset and throwing it away would make continuing the project unfeasible most of the time.
> It's a necessary liability for a sustainable software business, as without the code fixing bugs is nigh-impossible
Ok, so you agree with me, the code is a massive asset since it is the code that lets you change the program. Code is there to help programmers do their work, that is valuable as you say.
> But, the code itself is not what produces value. That's the code's output.
Both produces value. The code is valuable since it makes it easier to inspect and change the program. That is value. It isn't uncommon to sell source code access for products, why would anyone pay for that if seeing the source code is a liability? No, of course it is an asset, people arguing otherwise have just gotten too hooked on the "code is bad" mantra and try to argue all code is bad even though code obviously is very valuable.
Edit: And to be even more clear, throwing away the code and trying to work directly with a program binary creates technical debt, that is way worse debt than keeping the source code around. Code isn't inherently technical debt.
Code produces value for programmers, but none for end users. Software businesses can thus usually only charge end users for access to the runnable programs (or running programs, in an SaaS business's case).
You're certainly right that when a business's customers include programmers who want to extend or modify the programs, the source code can be an asset. I was mistaken to say categorically that code's never an asset.
On average, though, I think it's still more often a liability on the balance sheet.
It's why we lionize programmers who cut out thousands of lines without removing any features - they reduced the liabilities while retaining all the assets.
Then the equation is simpler. More code = more to maintain = more liability. It says nothing about the quality of the code, just the quantity.
Tech debt is a trade-off between near term and long term ambitions - it is literally impossible to build a successful project without accruing this kind of debt, simply because you cannot perfectly anticipate the future. In that sense, it's not so much debt as it is a maintenance overhead.