The Benefits of Modular Programming/Page 2
The Benefits of Modular Programming
2.3 A Modular Programming Manifesto
No one is surprised anymore that operating systems and distributions are designed in a modular way. The final product is assembled from independently developed components. Modularity is a mechanism to coordinate the work of many people around the world, manage interdependencies between their parts of the project, and assemble very complex systems in a reasonably reliable way.
The value of this approach is finally filtering down to the level of individual applications. Applications are getting more and more complicated, and they are increasingly assembled from pieces developed independently. But they still need to be reliable. Modular coding enables you to achieve and manage that complexity. Since applications are growing in size and functionality, it is necessary to separate them into individual pieces (whether you call them "components," "modules," or "plugins"). Each such separated piece then becomes one element of the modular architecture. Each piece should be isolated and should export and import well-defined interfaces.
Splitting an application into modules has benefits for software quality. It is not surprising that a monolithic piece of code, where every line in any source file can access any other source file, may become increasingly interconnected, unreadable, and ultimately unreliable. If you have worked in software for a few years, you have probably been on a project where there was some piece of code which everyone on the team was afraid to touch—where fixing one bug always seemed to create two new bugs. That is the entropy of software development. There is economic pressure to fix problems in the most expedient way possible—but the most expedient way is not necessarily in the long-term interest of the codebase. Modular software limits the risk of creeping coupledness by requiring that different components of the system interoperate through well-defined API contracts. It's not a silver bullet, but it makes it considerably harder to have the sort of decay that eventually dooms many complex pieces of software.
Comparing modular design and traditional object-oriented design is a lot like the comparisons of structure programming with spaghetti code from the 1960s. Spaghetti code was the name given to Fortran or BASIC programs where every line of code could use a
GOTO statement to transfer execution to another place in the program. Such code tended to be written in such a chaotic way that often only the author of a program could understand the program's logic. Structured programming tried to reduce this disorder by introducing blocks of code:
if statements, procedures, and calls to procedures. Indeed, this improved the situation and the readability and maintainability of applications increased. If nothing else, one could be sure that a call to a method will return only once. [Except for boundary conditions where it may never return, throw an exception, etc.]
The classic object-oriented style of programming in some ways resembles the situation before structured programming arrived. With the term "classic object-oriented style," we are referring to the style of programming typically taught today. It is also the sort of code you get from using UML tools: heavy use of inheritance, and almost everything overridable and public. In such an application, any method in any class may potentially call almost any method of any other class. Indeed there are
protected access modifiers, but the granularity of access permissions is done on the level of a single class or class member. That is far too low-level to serve as a basic building block of application design. Modularity is about the interaction between systems, rather than between small parts of subsystems.
Modular applications are composed of modules. One module is a collection of Java classes in Java packages. Some of these packages are public, and public classes in them serve as an exported API that other modules can call. Other classes are private and cannot be accessed from outside. Moreover, to be a module, a library must list its dependencies on its surrounding environment—other modules, the Java runtime, etc.
Inside a module, one can still apply bad coding practices, but the architecture of an application can be observed by checking the dependencies among all its modules. If one module does not have a dependency on another, then its classes cannot directly access the other module's classes. This keeps the architecture clean by preventing
GOTO-like coding constructs that could otherwise couple completely unrelated parts of the codebase.
Sometimes people say that their application is too small for modular architecture to be applicable. It may indeed be so. But if it is beyond the level of a student project, then it is likely to evolve over time. As it evolves, it is likely to grow. And as it grows, it is very likely to face the "entropy of software" problem.
The initial step in designing a complex application is to design its architecture. For this, it is necessary to define and understand the dependencies between parts of the application. It is much easier to do this in case of modular applications.
Thus it is always wise to start designing any application in a modular way. Doing so creates an infrastructure that will let you build more robust applications and avoid a great deal of manual bookkeeping. Rewriting messy, interconnected traditional object-oriented applications to give them a good modular design is a hard task. And it is not often that a project can afford the time it takes to be rewritten or rearchitected. Often, programmers have to live with old, monolithic code with ever-increasing maintenance costs—because the code is known to work.
Modular design starts you out in an environment where the architecture cannot slowly decay into unmaintainability without anyone noticing. If you create a new dependency between two parts of a modular application, you need to do some explicit gestures to set up that dependency. It cannot happen by accident. While that is not a cure for messy designs, it is an environment that encourages well-thought-out ones.
Modularity gives systems clearer design and control of module interdependencies; it also gives developers more flexibility in maintenance. Consider that when starting any new project—regardless of the project's initial scope. Modular design will have large benefits for the architecture of the entire application as it grows from its infancy. The real benefits of modular programming might not be apparent in the first version of an application. But they will become obvious later with the reduced cost of creating the 2.0 and 3.0 versions. Since modular programming does not add significant cost to creating the 1.0 version of an application, there is little reason not to use this approach on all projects. Many programmers are surprised (even sometimes horrified) to find something they wrote fifteen years ago still in use. Since we cannot predict the future of our code, we might as well architect it to last from the start.
2.4 Using NetBeans to Do Modular Programming
Creating a skeleton for a new NetBeans module is as easy as creating a plain old Java application project in the IDE. Just start NetBeans IDE, select File | New Project, and choose NetBeans Plug-in Modules | Module Project. Give the module a name and a location and you will end up with a brand-new project opened in the Project window. Create a Java class in the package that has been precreated for you. Then, in the IDE's Source Editor, use any the features of the JDK platform that you are running against. All the classes from the JDK are accessible and can be used without any special setup.
A slight difference compared to plain Java coding is the way one uses additional libraries. Instead of directly choosing a JAR file, one creates a module dependency on another module. To do this, open the Libraries subnode of your project and invoke the Add Module Dependency dialog. NetBeans contains a lot of modules and you can choose which ones to add as libraries. There are also many library wrapper modules, each presenting a third-party library as a standard NetBeans module. For example, there is Apache.org's Commons Logging library—just type
common, select the library, and click OK (Figure 2.1). Now the editor knows how to use logging classes so you can safely type, for example,
name.of.your.log")and the result will be compilable. This may not look like a huge advantage over selecting the JAR file directly; however, notice that it was enough to specify just the identifying name of the module and the Library Manager dialog located it for us. Adding explicit dependencies to a module project is quite simple.
It appears that creating a single module and reusing existing resources is fairly easy. However, the power of modular applications is in having multiple modules with dependencies, so the question is, how to create such modular application inside NetBeans IDE? The answer is a module suite. A suite is a container for a set of modules which can see each other and communicate among themselves (Figure 2.2). To create a suite, choose NetBeans Plug-in Modules/Module Suite in the New Project wizard. Then follow the steps in the wizard. After clicking OK, a new project be visible in the Projects window. In contrast to the regular project, a suite does not have its own sources. It is merely a project to bind together other, interdependent NetBeans module projects. So, choose Suite/Modules, invoke a popup menu on that node, and, using the Add Existing menu item, add the previously created module into the suite.
A suite can contain more than one module. To demonstrate that, we can convert an existing library into a module in the suite. Choose New Project again and select NetBeans Plug-in Modules/Library Wrapper Module Project, choose some plain existing Java JAR file, such as an Apache library, and follow the steps to create its wrapper and make it part of the suite we just created. When done, the suite will show the two modules under the Modules node and it will be possible to create a dependency between them. Select the first module again, right-click it, and choose Properties from the popup menu. In the Properties dialog, click the Libraries category to show the UI for setting up dependencies. Add a dependency on the just added library wrapper module. When done, classes in the first module will be able to use classes from the library.
Modules vs. Plugins
One point of potential terminology confusion is the use of the terms "plugin" and "module." For most practical intents and purposes, there is no difference. A module is simply a unit of code that you can "plug in" to the platform or the IDE. The term "plugin" has been popularized by various other environments and can easily be applied to modules created for the NetBeans Platform as well.
Traditionally, we have used the term "module" in the NetBeans environment, since the platform itself is composed of modules. (You cannot say that the core platform is composed of "plugins.") On the other hand, if you are creating new features for the IDE or the platform but your feature set is composed of multiple modules, you might prefer to refer to those modules collectively as a single plugin.
This chapter is excerpted from the book titled, Rich Client Programming: Plugging Into the Netbeans Platform, authored by Tim Boudreau, Jaroslav Tulach, and Geertjan Wielenga, published by Prentice Hall, as part of Sun Microsystems Press, May, 2007, ISBN 0132354802, Copyright 2007 Sun Microsystems, Inc.