Change is the Value of Software
Exploring the implication behind concepts like "tech debt" and SRP, that flexibility (ease of change) is a large factor in preserving the economic value of your software.
At some point in my early career I started hearing quips like "all code is a liability" and "the best code is no code at all." To the extent that these statements are true, it may be illuminating to think about why they are useful ideas.
The Debt Metaphor
One reason code might be framed as a liability is that each line of code is an opportunity to embed assumptions about the future that may be violated when the system is changed later on.
If one assumption in your system changes and you have to change behavior in locations, expect change cost, where is the weight of the assumption that changed.
To the extent your chosen design requires future changes that you may otherwise have avoided, that design can be framed as a liability, in the sense that you "owe" that future cost of making those additional changes later on.
Sometimes developers choose this deliberately, trading speed (and thus lower cost) today for higher change costs tomorrow.
In theory there exists a hypothetical version of your system where at any given time, your design hides zero brittle assumptions; This implies that you have perfect knowledge about future requirements changes.
With perfect information at the outset, change costs approach zero!
If our logic is flawless and never changes, we don't even need software anymore. We could etch our logic directly on custom silicon, and increase our margins by reducing electricity costs.
But we never operate with perfect information. So in some sense, all software has a debt component when you weigh it against hypothetically-perfect hardware that flawlessly solves a fully-understood problem.
The "borrowing" against the future that we do in our designs, when we do it well, is a form of leverage.
It buys us time to better understand the problem we're solving as we go; to better understand the world we are attempting to model.
All code is a liability. Great!
The goal, then, is to minimize this liability, to determine the optimal amount of debt to carry: whichever amount maximizes the project's total economic value over time.
Minimizing Debt
Let's stretch the metaphor a bit further and think about debt minimization.
We have already established that we can't make perfect designs without having perfect information. Some future change cost in our system is inevitable.
If we choose a suboptimal design, we are "liable" for the cost of improving the design as we get more information.
Sometimes this gap is intentionally large, when we buy speed today at the expense of future rework tomorrow.
The less brittle that design is, the lower our cost of change, and therefore, let's say, the less interest we pay on our "debt" as we go along?
We can derive the next cliché as a simple value judgement:
Good software is inexpensive to change.
Under this definition of "good" tied to cost of change, the effective interest rate on our design debt falls on a spectrum of flexibility.
The cheaper our future changes are, the less interest we pay, and the more value we retain.
At one side of the spectrum, then, we have:
Software that cannot change has minimal value.
At the opposite extreme, we get:
Software that changes instantly has maximal value.
On its surface, this spectrum seems somewhat obvious, even definitional.
As we implied earlier, this is literally the meaning of software, as contrasted with the meaning of "hardware."
Once I get past the definitions and focus on the spectrum of flexibility, and the cost/benefit thereof, I think there are interesting ideas to explore.
Consider the challenge of finding the right degree of flexibility such that we maximize the economic value of our software in a given context:
- Flexibility is not free. Abstractions have a maintenance cost and a performance cost. How can we know we've made the right tradeoffs?
- Can we quantify the additional flexibility we get by adding additional indirection? What is the unit of and how do we measure it?
- Can we measure the economic value of that flexibility for this-or-that subsystem?
- Can we measure the cost of maintaining that flexibility over time?
- What general techniques exist to maximize the value of that flexibility?
SRP and Sources of Change
Here's one answer to that last question above: considering sources of change has a noticeably positive impact on the quality of my designs.
When I attempt to minimize cost of change, I often find myself trying to understand the sources of likely changes in the domain of the problem I'm solving.
This approach is congruent with the Single Responsibility Principle, which states that a unit of code should have only one reason to change.
When I'm designing a subsystem or module, I try to imagine what important decisions I may want to either reverse or adjust in the future. I try to write them down, make them distinct by creating physical/lexical separation, and make those choices as legible in the code as possible.
Doing this as as an explicit process step in my workflow helps isolate and "hide" those decisions behind interfaces more deliberately.
Interfaces have specific names, which makes them more easily searchable in a codebase.
To the extent you can map each important decision to some interface or similarly declarative, legible construct, it becomes trivial to see how much of your system depends on any one decision. Just CMD+F!
This is easier said than done, but if change is the value of software, then maximizing flexibility as deliberately as possible seems like the right stance to take in most circumstances.