Why We Shouldn’t Design Software the Same Way We Design Physical Things
Designing and developing software is a complex undertaking. But we often make it harder than it needs to be by approaching it as if we were building a physical thing, and by mimicking the design practices associated with them.
It seems the more ambitious the software we intend to build, the more we lean on designing it all up-front. This tends to work well for physical things, but can often backfire with software.
Particularly when we’re designing software for use by other people—and even more so when they will build upon it or use it as a foundation to solve their own complex problems—our designs and plans might make us feel more confident, but they aren’t always a great predictor of the outcome; the usefulness and value of the software we actually end up with.
The distance between software that hits the spot and software that falls way short is tantalisingly small, and incredibly hard to reason about in abstract. Despite our best intentions, our plans always seem to hide a number of assumptions about the end result which can turn out to be wrong.
Most traditional design activities are concerned with physical things that can be reasoned about and visualised, even if they are so complex that we need to do that in smaller chunks, layers or slices. Up-front planning makes good sense here and actually de-risks the outcome.
While it is theoretically possible to design and reason about software in similar ways, the sheer complexity of internal state, inputs and outputs to be modelled would probably hinder rather than help most projects in reaching a usable result. It gets even more complex when we introduce aspects such as user interfaces, user experience, distributed systems, machine learning, data science, etc. Even if we can reason about such software in smaller pieces, the system as a whole still contains a multitude of untested assumptions to throw our predictive powers way off.
So if our forwards-facing designs for software can be so misleading, why do we cling to them?
Software is clearly different. We seem to forget that it is easily and infinitely changeable, mutable, transformable. Unlike physical things, software can be one thing when first coded and then an entirely different thing in under five minutes. You obviously can’t say the same of a bridge, a chair or even of electronics, which is why they must be pre-planned in detail.
So why don’t we take advantage of the flexibility of software by incrementally changing it to become what it needs to be? This means being less invested in forwards-facing plans and more engaged in pursuing value via exploration and experimentation.
But if we don’t plan up-front, what would we aim for as we iterate and explore?
We should head as fast as possible towards anything that validates the assumptions that are inherent in what we believe we’re building… such as whether it will be of value, whether it answers a problem for users, or whether it is even possible to build it.
Almost everything we state as if it were a fact about the software we intend to build is merely an untested assumption. So let’s head directly for the ones that would be the most problematic or negative if they turned out to be wrong.
As for our design and architecture, they must evolve in parallel with the software rather than being entirely planned up-front before we start coding. Plans and designs become artefacts to explain and reason about what we’ve built and are building next, rather than ways to figure it all out up-front.
Sometimes the plan inches forward and the software catches up, other times we might code something to validate an assumption and revise the plan to reflect that. But crucially, the plans allow us to reason about what we’ve built so far, and will build next.
We should plan and architect only enough to see two things in each iteration: That our overall direction seems sensible at a high level, and that our next steps seem logical at a more detailed level. Then we build to validate the assumptions inherent in those plans. Finally, we modify our plans to reflect our findings and go deeper, either refining part of what we already built or extending it to cover part of a larger overall rough plan.
Doesn’t exploration result in sloppy, poorly-planned software?
The quality and rigour of what we're building can actually grow with each iteration: We harden pieces that seem to be of value, adding metrics and ways to understand and support what we’ve built. But we also change or throw away the bits that are proving to be of less value or which miss the mark. So quality gets built in as our direction and intended destination become increasingly validated, rather than by being pre-planned in up-front.
Pre-thinking quality is not a guarantee that we will end up with it, so we should experiment, validate and bravely refactor it too.
Isn’t this cycle of exploration and disposal potentially wasteful of our efforts?
Overall, not really. Although we may lose what feels like a more direct path from plans to software, in reality that path is laden with assumptions and risk. So by exploring and validating, we vastly improve the likelihood that what we end up with is successful.
Do engineers need a different mindset to work like this?
Yes. Engineers must feel empowered to propose refactoring of relevant aspects across an entire system, rather than being siloed to localised changes. Obviously, multiple eyes must agree on those changes and they must actually work, but the encouragement to think about the system as a whole should be there. Think big, rely on and increase test coverage, and refactor everything that needs to change to move forward.
With software, you can and should change the foundations if new information suggests they need revising. Our tendency to think of software as an immutable physical thing is largely what dissuades us from considering this. Prior decisions are (quite literally) not set in stone.
Engineers and leadership must however own and take responsibility for the scope of ongoing iterative change required, its impact on other moving parts, and for increasing their skills/awareness to handle all of that well. In my experience though, engineers are more than willing to step up once invited out of their silo.
An iterative path to software design and development might seem longer and less disciplined than up-front approaches, but the software we end up with is more likely to be of actual value.
And, perhaps somewhat counterintuitively, it is more likely to be well-designed.