Designing modular systems is fundamental for managing software complexity, unfortunately most developers don’t have a good grasp of its properties. It’s actually a concept that is more subtle than one would think.
So what are the fundamental principles of modularity anyway? What makes a system modular? Here’s what google finds:
The property of a system that has been decomposed into a set of cohesive and loosely coupled modules. Characteristic of the Object Design Model.
Dividing a system up into chunks or modules of a relatively uniform size. (2) See also Cohesion, Coupling.
The division of a system into "loosely-coupled" components, connected by pre-specified interfaces¤. See also "encapsulation¤"
(desireable) property of a system, such that individual components can be examined, modified and maintained independently of the remainder of the system. Objective is that changes in one part of a system should not lead to unexpected behaviour in other parts.
division of a program into units (modules) which have a well defined interface. modules are building blocks for a program.
A force design methodology that establishes a means to provide interchangeable, expandable, and tailorable force elements.
Signifies that a system or an operation can be implemented, realized, and modified in successive, homogeneous and coherent steps.
Dividing a system into chunks or modules of equal size.
A programming design methodology that breaks complex task down into smaller, simpler functions.
So let’s try to gather some key terms from the list above. “decomposed”, “dividing a system”, “loosely-couped components”, “pre-defined interfaces”, “modified and maintained independently”, “changes do not lead to unexpected behaviour in other parts”, “interchangeable”, “expandable”, “tailorable”. Consolidate all these terms together and you’ll concoct quite a solid definition1. Rather than derive a definition myself, I chanced on an excellent definition in the book “Design Rules: The Power of Modularity“.
Here are six core design operators essential for modular systems:
Splitting – Modules can be made independent. Substituting – Modules can be substituted and interchanged. Excluding – Existing Modules can be removed to build a usable solution. Augmenting – New Modules can be added to create new solutions. Inverting – The hierarchical dependencies between Modules can be rearranged. Porting – Modules can be applied to different contexts.
These operators are of a general nature and inherent in any modular design. Operators are actions that change existing structure into new structures in well-defined ways. In the context applied to software this can mean refactoring operators at the source code level, language constructs at specification time or can mean component models at configuration time. These operators are complete in that they are capable of generating any structure in computer design. Furthermore, it is parsimonious, it is smallest set of rules possible. Finally, unlike other definitions of properties of Modularity, it does not mention qualitative measures.
Let’s now briefly look at few offenders. One notable offender is Ant, where Mike Spille writes in “Ant is finally a real build tool!“:
… the few anemic nods to modularity have basically sucked, and has lead over and over again to people creating many big build.xml files that are mostly identical, and with little chance for creating corporate/departmental/project build standards beyond offering a reference build.xml file – and instructing developers how to do cut and paste on their OS of choice.
Ant supports four operators for Modularity, the first is that it supports porting through the use of properties that allow them to be set depending on the context. The second is that it supports splitting through the use of an ant task that can call other ant tasks. Excluding and Augmenting are supported through the condition task and ants task dependency mechanism. However. substituting modules is clearly missing. Fortunately, as Mike Spille’s article states, Ant version 1.6 adds a new import task, that fixes the deficiency. Nevertheless, I’ve always felt that Ant’s mechanisms to support Modularity have always felt ad hoc furthermore there’s always this feeling of non-uniformity. Until a more thorough analysis is done, I don’t think I’ll feel entirely comfortable with its support for Modularity.
Another system that lacks the necessary ingredients is the class loader mechanism in Java. It’s described sufficiently is this article from Tangosol. The primary problem that is obvious is that configuration is in code and is implicit. Configuration as a general rule should be separable from context (see Dependency Injection), the ClassWorlds project is an attempt at providing an explicit specification of how Class Loaders are composed:
The classworlds model does away with the hierarchy normally associated with ClassLoaders. Instead, there is a pool of ClassRealms which can import arbitrary packages from other ClassRealms. Effectively, classworlds turns the old-style hierarchy into a directed graph.
ClassWorlds supports two directives, “load” supports specifying the location of a class source the belongs to a realm, “import” specifies how to include a class from another realm. In addition, system properties can be used, this supports porting. It’s an improved step over the original, however clearly it lacks substituting and excluding. That is, I should be able to reuse an existing “launcher configuration” from another file 2; furthermore, I should be able to exclude classes from an acquired Realm. Until the deficiencies are removed people will never be completely satisfied with their classloading mechanism.
In conclusion, it’s important to keep in mind the six operators of modularity, missing any one of the operators leads to a ugly workaround (a.k.a. hack) and ultimately an unsatisfied customer (a.k.a. developer). So, in the spirit of usable APIs, please provide support for all six operators the next time you design your next modular system.
1. One definition I find strange in the mix is “dividing a system into chunks of equal size”. I guess what is meant by “equal size” is substituability.
2. Alternatively by leveraging Groovy as shown here.