On the importance of freedom of choice in frameworks
Whenever you enter someone else’s private home, it is generally accepted that you must adopt that person’s customs. If the person tells you to take off your shoes, you should do so. You don’t throw your coat on the sofa unless the person explicitly tells you that it is okay. If the person is having you for dinner, you let the person choose what you will be eating, since he or she will be the one to cook it.
Compare this with going into a restaurant. When you enter a restaurant, you are the boss. You tell them what you want to eat. The owner (and his or her employees) are here to serve you. They are providing a service for you.
Developing software on someone else’s development framework should be similar to the latter. The framework writer released his library for your use; they are providing a service to you, much like the restaurant owner. Unfortunately, most framework developers seem to adhere to the former. They have designed a system to accomplish a very specific task (such as creating a website), and they generally have a very specific idea as to how to accomplish that task. More often than not, the framework only lets you structure your system in the same way that the framework author believes is “the best way”.
There are definite disadvantages to this “my way or the highway” approach. By hindering the ability of your client developers to come up with new ways to solve a specific problem, you are effectively stating that you know better than everyone else, which is of course never true. In fact, if your way of doing things is misguided, you’ll be doing a disservice to those who are learning development on your framework. Junior programmers do not have the experience or instinct to distinguish bad design, and they will adopt your way without question. It is also impossible for you to predict all of your client developers’ needs. If you provide a rigid framework, you will often end up with requests from your users that the framework is impossible to satisfy. You are then faced with two options: either restructure part of your framework to satisfy the needs that you had not anticipated, or simply tell them that the framework “wasn’t meant to solve that problem”.
Of course, relinquishing control and offering more freedom of choice tends to increase learning curve of your system, as well as introducing a higher risk of client-side bugs. The general solution to this conundrum is usually known as “convention over configuration”. Basically, this philosophy favours coding by convention instead of having to configure every single aspect of the system. This is fine and good, but most framework developers seem to interpret convention over configuration to remove choices and replace them with the most commonly-used options. That’s the Apple way! This is no good; we are back to square one.
Convention over configuration can only make sense when it is interpreted as “offering options to cover as many needs as is sensible to do, but offer meaningful defaults for every option so that developers don’t have to make a choice right away”. The best way to accomplish that is by developing a framework which has a modular design. Each “task” that your framework must facilitate should correspond to a separate module in your code. Each module is programmed in such a way as to execute the task in the most intuitive way (this is the meaningful default). But at the same time, provide a way for the client developer to override this module with his own custom code. That way, if he is stuck in a particular situation where the default functionality is not applicable, he always has the choice to “roll his own”.
Allow me to illustrate this with an example. Let’s take an object-oriented web development framework. Such a framework would need to facilitate many tasks; handling requests, cookie management, session management, data storage, etc. Let’s take session management as an example. Don’t juste write a session management class and dump it on your users. If you have other parts of your framework that use this class, your client developers are stuck using it! Instead, create an interface which describes the basic operations relating to session management (e.g. “ISession”). Then create a basic class which implements this interface in the most intuitive way (e.g. “BasicFileBasedSession implements ISession”). Finally, have a factory method (e.g. getSessionProvider()) which returns an instance of the class which is registered as the current implementor of ISession. That way, if the client developer is creating a website which spans multiple servers, BasicFileBasedSession is not sufficient for his needs, but no matter; he can create his own ISession implementation called DatabaseSession and register that as the default implementor of ISession. That way, even the other parts of your framework that use session management will be using his class.
All in all, it’s good to have a solid direction when designing a framework; but always let your client developers diverge from that direction whenever it makes sense.