Be Careful with Software Metaphors
Editorial Note: I originally wrote this post for the NDepend blog. You can check out the original post here, at their site. Head on over and check out that post and others as well.
Over the years, there have been any number of popular metaphors that help people radically misunderstand the realities of software development. Probably the most famous and persistent one is the idea that making software is similar to building a skyscraper (or to building construction in general).
This led us, as an industry, to approach software by starting with a knowledge worker “architect” who would draw grand schematics to plot every last detail of the software construction. This was done so that the manual laborers (junior developers) tasked with actual construction could just do repetitive tasks by rote, deferring to a foreman (team lead) should the need for serious thinking arise. It was important to lay a good foundation with database and framework selection, because once you started there could be no turning back. Ever. Should even minor plan changes arise during the course of the project, that would mean a change request, delaying delivery by months.
Software is just like construction, provided you’re terrible at building software.
This metaphor is so prevalent that it transcended conscious thought and crept its way into our subconscious, as evidenced by the “architect” title. Given the prevalence of agile (or at least iterative) software development, I think you’d be hard pressed to find people that still thought software construction was a great model for building software. I don’t think you see a lot of thinly sliced buildings, starting with an operational kitchen only and building out from there.
But there are other, more subtle, parallels that pervade the industry and lead to misunderstandings between “the business,” managers, and software developers.
One such misunderstanding that I see frequently is to equate software components with physical components. Consider a small application that consists of a login screen, a profile screen, and a screen that allows users to browse and make purchases. There’s a natural tendency for people not involved with the code — particularly non-technical people — to view these as three isolated components.
The mental model thus becomes one of assembly. This small project is like setting up a bedroom with disassembled furniture. There’s a bed, a dresser, and a night stand, so what you do you when you have a tight timeline and plenty of labor? Naturally, you create a bed team, a dresser team, and a night stand team and task them with working in isolation. Once the individual components are ready, they can be integrated by positioning them appropriately within the room. Right?
It’s the perfect plan, but it seems like the assembly teams can’t figure it out. They keep talking about things you don’t care about, like databases, session management, and something called “common,” whatever that means. So you tell them to have more meetings and figure it out. But then they come back and talk about how it isn’t a good idea for two different teams to implement “leg.” You patiently explain that a bed leg is different than a night stand leg or a dresser leg, and tell them to each make their own, and that you don’t know or care what “DRY” means.
Building isolated, pluggable components is a good idea, it makes business sense, and it allows you to pipeline labor. Good developers will figure out how to make that sound plan a reality. Right?
Not so much. As it turns out, software components and physical components have some key differences. Replicating a physical component involves construction, fabrication, or 3-D printing, whereas replication of a piece of software involves flipping a bunch of bits on a disk. Reusing a physical component means hacking it off of a nightstand and taping it to a bed. Reusing a software component is not destructive this way. The differences go on, but the point is the same. Asking a software team to operate as if it were building physical components is a recipe for friction between your mental model and theirs.
This is liable to raise some eyebrows, because the concept of “technical debt,” is, perhaps, one of the best tools when it comes to facilitating a conversation between developers and people making budget decisions. Technical debt (more or less) refers to a situation in which developers take a shortcut to get something to market in a hurry, knowing that they’ll later have to undo their current work and “do it right.” In other words, they’re paying a premium for short term liquidity, the way someone who incurs financial debt does. They’ll later have to ‘repay the interest’ by spending more total time getting to the right solution.
Unlike the metaphor of building construction, which I would argue has been largely damaging to the industry, financial debt as a metaphor has proved quite valuable. But it has limits, and carrying the metaphor too far can lead to misaligned expectations.
When you take a loan from the bank, there are clear terms to payoff. Generally this means that you’re constantly paying a small percentage of what you owe as a premium for the outstanding balance of the loan, and that percentage either doesn’t change, or it changes predictably. With software? Not so much.
Even assuming that it were straightforward to quantify the “less effort now for more effort later” trade off, you wouldn’t get a constant rate or even a predictably changing rate. Software is a lot more volatile than that, and the “return rate” will depend on a lot more than what you’ve “borrowed.”
To put it in a way that’s perhaps easier to sink your teeth into, consider the software construct of “global state.” If you don’t know what this is, think of it as a super power that software developers have to rip holes in the space time continuum, at least as it pertains to the world of software. Let’s say that your software is a city, and there’s a traffic jam preventing you from shipping. “No problem,” your developer says, “I can take care of that if you don’t mind some technical debt.” She then proceeds to rip a wormhole into the middle of a busy street, diverting all traffic into what looks like some kind of desert somewhere. Traffic problem solved. Ship it.
You’ve taken out one single loan in this universe to get traffic to a manageable level. Granted, that particular road is ruined by the wormhole, so you’ll need to build another one at some point, and that’s the interest you’ve agreed to pay. That’s what you’re planning on doing when you get around to it. And that all seems fine until you start getting weird reports of camels blocking traffic miles away, and scorpions stinging people going to work on traffic lights.
It turns out that ripping holes in the very fabric of your application has weird, unpredictable consequences that require repayment of debts you never planned for (and perhaps don’t understand the source of). The lesson is that a decision to let (or encourage) developers to take shortcuts and make hacks can put your code in a degenerative state that neither of you is really prepared for. If you aren’t careful, pressure to get them to ship will be more like navigating a minefield than shopping around for a mortgage.
It’s legitimately hard to mentally model software, particularly if you’ve never been a developer. We live in a very tactile world and we use vivid mental models as mnemonics to aid our understanding. Software is very abstract, conceptual, and precise in nature, and this makes it inordinately difficult to model in the way to which we’re accustomed. We’re bad at bridging these worlds, and it’s perfectly understandable that we’re bad it.
Frankly, the only way to have a good mental model of software is through practice, and the realization that any analogies we use are transitory and incomplete at best. Holding too close to any particular metaphor is liable to trip you up in your decision making, so be very wary in conceiving of software development as being like anything other than… software development.