Note: This is a copy of a page I wrote for the software engineering course I taught at the University of Illinois Urbana-Champaign. I am reposting it here on my blog in the hope that it will be found useful by others in the future.
According to McConnell, a module is either (1) a collection of data along with routines, or functions, that use or manipulate that data or (2) a collection of routines, or services, that operate on any external data given to it. Modular Programming aims to maintain this type of structure in code by stipulating that all classes or routines be independent(de-coupling) as much as possible without inhibiting their ability to interact (cohesion). In other words, a truly modular program is one in which cohesion is maximized and coupling is minimized. Why should one code in this way? The answer lies in the fact that modular programming has proven, with actual experimental studies, to be more maintainable and easier to debug. Since this course involves writing a fair amount of code, we advocate the modular programming approach. We describe some good rules of thumb in the sections to follow.
Cohesion vs Coupling
Cohesion and Coupling are two important aspects of modular programming that need to be well defined before one starts writing modules. A module is cohesive if it offers services that are all related to each other , particularly in terms of high level functionality. For example, if a module contains the functions enqueue() , dequeue() makePhoneCall(), writeToDisk(), readTextFile() . Perhaps the type of data used is uniform for this program, but many of these functions have nothing to do with one another. The correct solution to making this a modular program is to group the functions that are related to each other into separate modules. With this, a supermodule can be created to connect these submodules. A module is decoupled and independent if it allows for other modules to interact with it very easily without having to use any additional “hacks”. De-coupling requires one to understand the parts of the module that are independent from each other. You can think of independence by asking “how much does this function or subclass affect this other function or subclass”? If the answer is ” a lot” , then one should maintain the code as it is, making a note about how these two components are highly related. If the answer is “very little”, which typically would be the case for programmers new to modularity, then it is best to decouple the two components into separate submodules.
As the term suggests, information hiding aims to prohibit others from viewing private areas of one’s program. Users of one’s code need only to know the interface of the code and not the implementation. Thus, one would only expose the code’s interface while hiding the private implementation details. According to McConnel, Such private areas are typically:
- Areas likely to change frequently
- Complicated data within a module
- Intricate Logic with routines of the module
- Operations at the programming-language level
As an example, suppose you are writing a program for your company and it involves data about the number of employees at the company. One may not want to tell outsiders or competitors about the size of its company; it might hurt that company financially to do so. Instead of making the company size publicly viewable you would have an interface that only gives public information (Gender, age, ethnic demographics as a percentage but not the actual numbers). What is being hidden is how those percentages are being calculated since the company size would be needed to calculate such demographics.
To make sure that you are following modular design, here are some principles to adhere to:
- A module should address one central functionality or goal.
- If a module is built from other smaller components, a module should be easily broken down into these components.
- Implementation details of the module should be hidden from external modules. It should be seen as a black box.
- The interface of the module should allow for easy access to its services without needing to set or hardcode any other information.
- The set of services that the module provides should be related, in terms of high-level functionality, to each other.
It is important to realize that once you have a fully functional module, you can use this module as part of a larger module containing submodules as its components. This idea defines the recursive nature of modular programming, in that you can always follow this principle to build larger and larger programs. The “super-module” should follow all the principles discussed above, maintaining maximum cohesion and minimal coupling with other such modules.