Simplicity Before Generality, Use Before Reuse
A common problem in component frameworks, class libraries, foundation services, and other infrastructure code is that many are designed to be general purpose without reference to concrete applications. This leads to a dizzying array of options and possibilities that are often unused or misused — or just not useful.
Generally, developers work on specific systems; specifically, the quest for unbounded generality rarely serves them well (if at all). The best route to generality is through understanding known, specific examples, focusing on their essence to find an essential common solution. Simplicity through experience rather than generality through guesswork.
Favouring simplicity before generality acts as a tiebreaker between otherwise equally viable design alternatives. When there are two possible solutions, favour the one that is simpler and based on concrete need rather than the more intricate one that boasts of generality. Of course, it is entirely possible (and, it often seems, more than a little likely) that the simpler solution will turn out to be the more general one in practice. And if that doesn’t turn out to be the case, it will be easier to change the simpler solution to what you now know you need than to change the “general” one that turns out to be more complex, but not quite general enough in the right way.
Although well meant, many things that are designed just to be general purpose often end up satisfying no purpose. Software components should be, first and foremost, designed for use and to fulfil that use well. Effective generality comes from understanding, and understanding leads to simplification. Generalisation can allow us to reduce a problem to something more essential, resulting in an approach that embodies regularity across known examples, a regularity that is crisp, concise, and well grounded.
Too often, however, generalisation becomes a work item in itself, pulling code in the opposite direction, adding to the complexity rather than reducing it, running up technical debt rather than repaying it. In Refactoring, Martin Fowler describes speculative generality:
Brian Foote suggested this name for a smell to which we are very sensitive. You get it when people say, “Oh, I think we need the ability to do this kind of thing someday” and thus want all sorts of hooks and special cases to handle things that aren’t required. The result often is harder to understand and maintain. If all this machinery were being used, it would be worth it. But if it isn’t, it isn’t. The machinery just gets in the way, so get rid of it.
The pursuit of speculative generality often leads to solutions that are not anchored in the reality of actual development. They are based on assumptions that later turn out to be wrong and offer choices that later turn out not to be useful. The consequence? Difficulty. Of course, software development is no stranger to difficulty. In No Silver Bullet Fred Brooks observes:
To see what rate of progress we can expect in software technology, let us examine its difficulties. Following Aristotle, I divide them into essence — the difficulties inherent in the nature of software — and accidents — those difficulties that today attend its production but that are not inherent.
Which raises the question:
How much of what software engineers now do is still devoted to the accidental, as opposed to the essential?
Speculative generality accumulates baggage that becomes difficult or impossible to shift, thereby adding to the accidental complexity those in development must face in future.
Although many architects value generality, it should not be unconditional. People do not on the whole pay for — or need — generality: they tend to have a specific situation, and it is a solution to that specific situation that has value.
We can find generality and flexibility in trying to deliver specific solutions, but if we weigh anchor and forget the specifics too soon, we end up adrift in a sea of nebulous possibilities, a world of tricky configuration options, overloaded and overburdened parameter lists, long-winded interfaces, and not-quite-right abstractions. In pursuit of arbitrary flexibility, you can often lose valuable properties — whether intended or accidental — of alternative, simpler designs.