Premature generalization can happen when we incorrectly widen the scope of a problem, or try to anticipate use cases before they happen. It often comes about when we attempt to make a multi-purpose, or re-usable solutions before fully understanding what is really needed.
Premature generalization is a trap we can fall into just about anywhere we are trying to solve problems: construction work, product design, and carpentry. Here I am mainly interested in how premature generalization can be a problem in programming and writing software.
Why Do We Generalize?
There are plenty of good reasons to generalize when designing and writing software – we often know that we have a number of similar cases to deal with so that it makes sense to build a framework or abstraction to help us.
At a high level, generalization can involve attempting to make solution that is not just specific to one exact problem, but that is extensible to more general problems. At a lower level, generalization may involve drawing together common features or classes into a library as good practice in software design often involves avoiding duplication and reducing coupling of code.
Sometimes, however, our reasons for generalizing are not so sensible:
- we just want to make something cool or try out a new design pattern. It feels much more fun to be working on a project that can process any file type under the sun, rather than the two file types you know it will actually need to deal with.
- Our enthusiasm gets the better of us – we have a great idea for our program and can see the potential applications beyond the immediate problem we are working on.
- Our inexperience means we want to try out everything we can in our project – using every tool for the job rather than the right tool.
What ever the reason for generalization, doing it too soon can be problematic.
Premature Generalization Is Bad
Premature generalization often happens because we are unfamiliar with the problem we are working on, or we over-estimate our ability to find a solution. Scott Wiersdorf has written that it’s when working under these conditions that any solution he comes up with “is suboptimal in the best case and flat-out wrong in the common case”.
Premature generalization is not necessarily the root of all evil, and you will likely produce a working solution irrespective of when you generalize. The problem is that your code is likely to be more difficult to develop, and maintain than it needed to be. Once you have started down the path of a wide-spread, generalized solution, it can be difficult to change direction.
How to Avoid Premature Generalization
Knowing when it is appropriate to generalize can be difficult, but there are some principles you can try to keep in mind.
The Rule of Three
A good thing to remember is that you can’t make something reusable and generalized unless you have at least three examples to work with. A rough rule of thumb is to not attempt to generalize until you have at least three real use cases to work with. With these real-world cases you can identify the common traits and create genuinely useful abstractions of them. More real-world use cases will help you define and refine the abstractions you need. Of course if you try to gather too man use cases, you run the risk of delaying generalization too much.
You Ain’t Gonna Need It
You Ain’t Gonna Need It (or YAGNI) is another principle you can try to keep in mind when your mind is racing about the possible features and abstractions you could implement. Implement things when you need them, and not before.
Even if you feel completely sure that you will need to implement something in the future, it still makes sense to wait: often you won’t actually need it; even if you do it’s likely you’ll need something different from what you original envisioned.
Following YAGNI will help you by a) not having to write as much code b) Not cluttering your code with ‘just in case’ preparation for possible future uses.
The Simplest Thing That Could Possibly Work
Another way of approaching things is to build the simplest thing that could possibly work. This focus on simplicity is a philosophy borrowed from ‘Extreme Programming‘ . When considering options for what your program, class or module might involve you should aim for simplicity at first, and only later modify your code when ‘what works’ changes to include more use cases.
Refactor: Code First Generalize Second
Being prepared to refactor your code can also help you avoid generalizing at the wrong time. You first write code to solve your specific, immediate problems, and only then do you look for the abstractions that can generalize your solution. By starting from a concrete example of useful code, you will have a much better idea of which generalizations will be beneficial.
Premature generalization can come about from being too concerned with adherence to coding standards and getting things absolutely ‘perfect’. Keeping things clear, explicit and concrete, even at the expense of repeating yourself occasionally may help you get a better grasp of the problem, and open your eyes to better, more optimal solutions.
So try to relax away from always being doing things perfectly, and write your solutions knowing that you will want to refactor them later.