A team finishes a feature. It works. The tests pass. The demonstration goes well. By the usual measures, the work is complete.
Then someone asks for a small variation.
The new requirement looks modest, but implementing it means touching several unrelated parts of the system. A change in one place unexpectedly affects another. A developer hesitates before making an improvement because the consequences are difficult to predict.
This is a familiar pattern in software development. The cost of a feature is visible when we implement it; the cost of changing that feature is often revealed later.
The first version is not the whole story
I have spent much of my working life studying software design, working with teams, and exploring ways to make systems evolve more easily. One thing has become increasingly clear to me: the first successful implementation is not enough to judge a design.
The first version answers a question about the present. The next version tests the decisions we made about the future.
A design is useful when it lets us make meaningful changes without paying for the same understanding again and again. That does not mean predicting every future requirement. Trying to anticipate everything usually creates its own complexity.
It means making the system understandable enough that we can respond when the future arrives.
Complexity is often relational
We tend to describe complexity as if it were a property of an individual function, class, or module. Sometimes it is. But much of the difficulty of changing software comes from relationships: what depends on what, what we need to know before touching a component, and how far a decision propagates.
A small function can be expensive to change if many other decisions depend on it. A larger component can be manageable if its responsibilities and connections are clear.
This is one reason I am interested in the relationships between elements of a system rather than only the elements themselves. It is also part of the territory I am exploring through Recombining Relational Production® (RRP®).
Three questions worth asking
Before calling a design finished, I find it useful to ask:
- Can someone understand why the current structure exists?
- If the requirement changes, which parts of the system will need to change with it?
- What can we learn now that might make the next change less expensive?
These are not metrics by themselves. They are ways to direct our attention toward consequences that are easy to ignore when we focus only on delivering the next feature.
Growth should be a design challenge
Software should be able to grow. But growth should not automatically mean accumulated friction. If each feature makes the next feature more difficult, something important is happening beneath the surface of our delivery process.
The question I keep working on is ambitious: Can we grow software while reducing its complexity and the cost of change?
I do not think the answer will come from one programming language, one framework, or one diagram. It will come from examining design decisions, their relationships, and their effects over time.
Discover more from Francesco Cirillo
Subscribe to get the latest posts sent to your email.
Like this:
Share this update: