decoupling in depth
In his influential paper On the Criteria To Be Used in Decomposing Systems into Modules, David L. Parnas offers some simple, timeless advice: if two things change together, they belong together.
This is not, in itself, an answer. It simply replaces a nebulous question — do these belong in the same module? — with something more concrete.
In this newsletter, we are trying to do something similar. Previously, we established that every change is an explanation. When we say two things are coupled, we mean that they tend to be co-explained. This co-explanation is out of necessity; their meaning is intertwined. One, or both, has explanatory power for the other.
And so, if we want to know if two things will change together, we should look to their explanatory power. If one thing explains another, they belong together. And if not, we should preserve that separation with an interface.
Consider a simple hash table. It's difficult to imagine an explanation which begins with our application's business logic, and ends with "and this is how a hash table is implemented." Our business logic has no explanatory power for the underlying data structures.
The converse is a bit more nuanced. There is a small but real possibility that we will give an explanation that begins with hash collisions, and ends with "and that's why our application is slow." But hash collisions are an incremental concept; we can still gloss over the remaining implementation details. It's difficult to imagine an explanation which begins with the in-memory layout of a hash table, and ends with our business logic.
We can find a similar pattern in SQL. While ostensibly a declarative language — it describes the result, not the process — we can often use different queries to generate the same result. Sometimes, the performance of these queries varies by orders of magnitude.
This is why many SQL implementations provide an EXPLAIN
command. Given a query, it will provide a plan for how that query will be executed. Like hash collisions, these query plans are an incremental concept; they explain more without explaining everything.
When we say two things are decoupled, we mean co-explanation is unlikely. Our goal is not perfect separation. UUIDs are only very likely to be unique. Checksums are only very likely to detect changes. Most explanations are simple, a few are not. Complexity should be weighted by frequency.
And so, interfaces for complex implementations are often layered. The purpose of each layer is to reduce the audience for the next. In military terms, the implementation is defended in depth:
Rather than defeating an attacker with a single, strong defensive line, defence in depth relies on the tendency of an attack to lose momentum over time or as it covers a larger area.1
Your readers are not determined invaders. They want the interface to suffice. Give them a bit of explanatory distance, and they'll find a reason to turn back.
This post is an excerpt from my (incomplete) book on software design. For more about the book, see the overview.