Designing code that is flexible, easy to apply for different purposes, is difficult. Every software engineer has experienced using both highly flexible and inflexible code. Using flexible code is much easier and more fun than the alternative. So we all learn to nod and smile when people talk about making code flexible and reusable.
But sometimes we forget the purpose of software engineering. Not all code needs to be flexible. Sometimes it is enough to dash off a quick piece of code that solves the current problem and be done with it. Sometimes code needs to be a little bit flexible, but only to solve a few related problems. The trick is to recognize the scope of the problem at hand. We often do this by thinking of use cases and trying to identify concrete ways that the code will be used. Use cases have to be real ways that the system will be used. (Not cool ways that it could be used “if we did it this way.”)
The temptation for many engineers, including myself, is to take a simple problem, try to think of all the ways a solution could be extended, and try to stretch the solution too far. But software design is a bit like a balloon: the more you stretch it, the thinner it gets, and if you go to far it may explode and leave you without anything useful. It’s easy to waste time thinking of harder problems than the one at hand. But every feature, every concept, every layer of indirection that you add to the solution adds to the complexity, which affects the implementation time, debugging time, and difficulty of modification in the future.
Sometimes we really do solve big problems that require highly flexible software. But the right approach is not to “think really hard” and take a psychic stab at the answer. Instead, bigger problems need more use cases; the number and scope of use cases is proportional to (and in fact, is) the size of the problem. The first step in design is to identify the uses of the software as much as possible, then design software that fits those uses—and no more. This came up the other day when I was thinking about a mini-UI-framework for an application I was creating. It needed to be a little flexible to handle several different UI cases, but it seemed like every solution was flexible in some ways and inflexible in other ways. Then I remembered that for the problem I was solving, I didn’t need an ultimately powerful framework, I just needed something to handle the half-dozen cases at hand. I chose a simple abstraction that fit those cases, and moved on. Any engineer could probably point at several reasons why I should have done it a different way (because of the various inflexibilities I introduced), but the fact is that I solved the problem of today, and we’ll let tomorrow worry about itself.
See related post by Kevlin Henney.