Share via


Future-Proofing Design

The first thing about designing software is this:

  • Design for change first and extensibility last.
  • They're different, and so is the mindset.
  • Designing for change is about solid fundamentals.

You know from experience that the later you make a change, the more expensive the change is. How do we reduce these costs? One way to go about it is to address the change earlier, when it's cheaper. You do more requirement-gathering and design upfront. At least you try to put extensibility hooks and some preemptive code in place for when the right time comes, then you can add new stuff without having to modify the existing stuff too much. It's a great idea, but it only takes you so far. Doing it well requires increased predictive powers, you must commit now to what you think you'll need later. Predicting the future is tricky though. Adding extensibility ahead of time is like buying extended warranty for a specific incident when you should be saving for any emergency, not just the particular ones you can foresee. Too much speculative stuff too early leads to additional complexity that gets in the way of changing the code right now for things you didn't predict. Then it becomes a self-fulfilling prophecy.

All along you were trying to reduce costs, so the first question to ask is not "how do I avoid writing this code later?", but "why is writing this later more expensive?" After all, if you can make changes cheap at any time, then suddenly you don't need to be as good at forecasting.

So what makes changing code expensive? The thing is, the cost of writing the code is roughly the same whether you do it now or later. It's the cost of understanding the context in which the code is to be written that increases. The messier the context, the more expensive it becomes to plow changes through it. Between a codebase that has no extensibility hooks in place, but is otherwise broken down in small, well-decoupled and well-encapsulated pieces, and a codebase with lots of extensibility and preemptive code built in, but otherwise made of big chunks of highly-entangled, spaghetti-woven pieces, the first one is better prepared for what comes next. It can reach into a broader set of possible futures than the one you predicted.

Simply put, the probability of any change happening is larger than the probability of a predicted change happening, for the former includes the latter, of course, so go with a design that has robustness around changes in general instead of one that tries too hard to preempt future requirements. At the end, future-proofing design is about solid fundamentals: low coupling, high cohesion, good layering, keeping things simple. The beauty about it is this: it's easier to tell if a design has good fundamentals than to try to predict the domain-specific requirements that will disrupt it.