Generalization is beautiful and exciting – and offers many glorious health benefits. It reduces the amount of code we have to write, and solves problems before they even arise. With just a tiny amount of forethought – we can make any future work we do trivial, simply by achieving the right generalization. It makes you more attractive to the opposite sex – and if done *just* right even grants you eternal life.
No wonder, like Elsa in the Indiana Jones movie, we neglect our own lives to attain it. “I can almost reach it, Indie”…
The Holy Grail, is of course, a myth. Though it makes for a good tale – sprinkle in some Nazis, some betrayal, some family tension. What a story.
In reality – the generalization that we all grasp for is also a myth. There is no way to know ahead of time what generalization will meet all of (or even one of) our upcoming use-cases. This is based mostly on the fact that we don’t know even what our upcoming use-cases are – let alone what kind of structure will be necessary to meet them. And the generalization you choose ahead of time, if it doesn’t match what you need in the future, is wasteful, because it limits the moves you can make. This is what generalization does, it limits expressive options to the abstraction that you select. And since you can never know the generalization you need, this always results in negative ROI. (Not as bad as falling into a bottomless pit, but still not exactly what we are going for)
The challenge is that it always SEEMS so obvious that it will result in nothing but advantage in the context we are working in, to generalize. This instinct is good – if you let it push you toward moderate flexibility in design, and refactoring (after you’ve solved a use-case) to a more generalized structure. Generalization that you arrive at AFTER you’ve learned what the use-cases will be (that is, after you’ve tested and coded them), and moderate flexibility in your design are both highly profitable. But they are both the result of disciplined, after-the-fact thinking; not a result of the magical thinking that we can somehow avoid work if we divine the right generalization before-the-fact.
This is also another reason that before-the-fact generalization seems so appealing – because it appears to give us something for nothing.
After-the-fact generalization that results in clean, easy to maintain code, that has a very positive return tends to seem simply like the diligence of a mature adult. Obviously the former, while maybe not tied to reality, is far more Rock-n-Roll.
As mature Craftsmen – we should do like Mr. Jones, and listen to the advice of his dad – “let it go, Indie, let it go…”
Once we’ve let this temptation go, we can take the following methodical approach – which will satisfy our impulse to generalize, but do it in a way that will result in a powerful, positive outcome.
- Solve the use-case(s) at hand, directly, with the simplest possible code. Use a test (or tests) to prove that you’re doing this.
- Solve with designs that are SOLID. SOLID leads to flexibility – flexible systems are easier to change systems.
- Refactor: remove anything creating a lack of clarity, generalize where there is unnecessary duplication.
- Rinse and Repeat
If we do this we will be creating amazing software!