Use Uncertainty As a Driver
Confronted with two options, most people think the most important thing to do is to make a choice between them. In design — software or otherwise — it is not.
The presence of two options is an indicator that you need to consider uncertainty in the design. Use the uncertainty as a driver to determine where you can defer commitment to details and where you can partition and abstract to reduce the significance of design decisions. If you hardwire the first thing that comes to mind, you’re more likely to be stuck with it — incidental decisions become significant and the softness of the software hardens.
One of the simplest and most constructive definitions of architecture comes from Grady Booch:
All architecture is design but not all design is architecture. Architecture represents the significant design decisions that shape a system, where significant is measured by cost of change.
What follows from this is that an effective architecture is one that generally reduces the significance of design decisions in the areas of greatest change. An ineffective architecture will amplify significance.
When a design decision can reasonably go one of two ways, an architect needs to take a step back. Instead of trying to decide between options A and B, the question becomes “How do I design so that the choice between A and B is less significant?” The most interesting thing is not actually the choice between A and B, but that there is a choice between A and B (and that the appropriate choice is not necessarily either obvious or stable).
An architect may go in circles, becoming dizzy before recognising the dichotomy. Standing at a whiteboard (energetically) debating options with a colleague? Umming and ahhing in front of some code, deadlocked over whether to try one concurrency model or another? When a new requirement or a clarification of a requirement has cast doubt on the wisdom of a current implementation, that’s uncertainty. Respond by figuring out what separation or encapsulation would isolate that decision from the code that ultimately depends on it. Without this sensibility the alternative response is often rambling code that, like a nervous interviewee, babbles away trying to compensate for uncertainty with a multitude of speculative and general options. Or, where a response is made with arbitrary but unjustified confidence, a wrong turn is taken at speed and without looking back.
Structuring by uncertainty is not new. Back in 1972, David Parnas outlined criteria to be used in decomposing systems into modules:
We propose […] that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.
There can be pressure to make a decision for the sake of a decision. But, as Voltaire noted, a sense of realism is important:
Uncertainty is an uncomfortable position, but certainty is an absurd one.
This is where options thinking can help. Where there is uncertainty over the different paths a system’s development might take, make the decision not to make a binding decision — a commitment — before its time. Defer the actual decision until a decision can be made more responsibly, based on actual knowledge, but not so late that it is not possible to take advantage of that knowledge.
Architecture and process are interwoven, which is why architects should favour development lifecycles and architectural approaches that are empirical and elicit feedback, using uncertainty constructively to divide up both the system structure and its timeline.