Specific vs. general: Which is better?
If you want your blog’s tag list to also show the tags' frequency, what is better? Adding the very specific feature of frequency computation to the blog engine or making it possible to supply a custom function that takes the whole program state and can return a new version of it (including e.g. tag frequencies)? I want to argue that in this case the latter is far superior.
A very specific feature - tag frequency - is arguably easy to understand and use. But with hundreds of users, how many such features, however small, would we need to make? How much more code for us, few and busy maintainers, to care for would this produce? How much more difficult would it become to get familiar with the code base, littered with tens of these tiny features?
On the other hand, there is nothing simpler than a function that takes a single argument (here the whole state of the blog engine at a significant point) and returns it, with optional modifications. There is essentially nothing to maintain. Of course it is more work for the user as she needs to learn what the data is and what change will have the desired effect. But it gives the user a lot of power. Power to implement features we would never have though of, that this one user finds really important.
Of course our blog engine - the awesome, simple static blog generator Cryogen, written in Clojure - already has a number of features and not just a few general extension points. So how do we decide to do the one or the other?
I actually think that it could be argued that the core of the blog engine could be implemented as a simple workflow (find files; pre-process them; derive additional data and pages; render) with all the details of each step hidden behind a simple interface - i.e. a general extension point - with the implementation provided by a library (though a default, extensible implementation could be included as a separate part of the code). If you come up with a really good abstraction for the process of generating a static blog site, with the right primitives, then you empower users to plug in their own functionality and combine various extensions made by others to create any custom workflow one might desire, without bloating the core. You give rise to a powerful tool that can satisfy anyone, and to a rich ecosystem of bits of functionality created by different users.
Where is the right balance between specific and general? As always, it depends.
Cryogen is in one kind of situation, where we have a number of power users - programmers and a tiny team, so it is in our best interest to create something understandable and extensible and thus we favour more general solutions.
But I would argue that generality should be applied more widely than it is, in general ☺️. Why? Because it enables simpler and more powerful solutions. They are simpler because, by definition, the code is (more) general and thus not coupled (so much) to the details of the specific situation / domain. Being less coupled, its "interface" with the rest of the system is smaller and thus easier to understand. It is by definition also more powerful because it is not fine-tuned to the single, specific use case and can thus potentially do more than the authors could think of. (As is the case with Cryogen’s
extend-params-fn from params + site-date → new params, where "params" is everything the engine knows about the blog and its files.) The authors of Software Design for Flexibility would, it seems, agree:
One should not have to modify a working program. One should be able to add to it to implement new functionality or to adjust old functions for new requirements. We call this additive programming. [..] To facilitate additive programming, it is necessary that the parts we build be as simple and general as we can make them.
Of course you can go too far in either way and make your code so general that it is incomprehensible. So balance you find must .