Saturday, October 20, 2012

Modularization


Once there was a time when software development was considered as a solitary effort and a programmer could sit with a design and go on with line after line of code. But modern software development is no longer a one man show. In fact software development is a collaborative effort, involving people with different skill sets to combine their expertise to produce a working product. Modern systems are large and involve various tasks as part of the development process. Various teams handle different aspects of a problem and develop subsystems which are later integrated to produce a complete system. To use all teams/people at their maximum productivity and to maximize the reuse of the code, a key principle of software engineering is to design in small, self contained units, called components or modules. When a system is created this way, it is called modular.
Modularization is the process of breaking a task into subtasks. The Modularization is the process of breaking down a problem / task / project / system into increasingly modular (which can be broken further) parts, revolutionizing the business models on which the aggregation of those small parts was based. The parts are broken on the basis of functionality or logic or the independence or their usability in other modules. The modularization also increases the reusability of the code and components. Often different modules may be used to create new products when aggregated with small / no changes.
The goal of modularization is to have each component meet following conditions:
  • Purposeful: A component fulfills a particular objective for which the component is created. We should avoid creating multipurpose components and break them into smaller single purpose components.
  • Small: The size of a component should be kept small and it should consist of an amount of information that a human can easily understand and maintain.
  • Simple: A component should be kept as simple as simple. The motive of modularization is to reduce complexity as most of the bugs arise because of the complexity of problems reflected inside the code.
  • Independent: A component performs tasks isolated from other components. A component’s objective should be independent of the objectives fulfilled by other components. A component may use other components’ public API as well but a component’s objective must not be to complement another module only. If it is so then it should be created as a subcomponent of the principal component whose objective is being complemented by this component.
A component (module) can be dependent on other components (modules) but the dependency should be well defined and a contract should be defined between the two components (modules) in terms of interfaces and methods etc. which should not get changed. If a component changes its public API, then all the components dependent on it will be broken.
In java a component should be designed in form of interfaces and the implementation of methods which are general to all kinds of the implementations of the interfaces should be done in abstract classes. One such example is the Collections framework of java which is designed as a set of interfaces and abstract classes. The interfaces and the methods specified inside the interfaces are the contract to which the component will abide. Other components may use the implementation of the interfaces without even knowing about the actual classes which implement the interface. This kind of design is also known as design by contract (D B C).
A component (module) is managed as a single unit. While working in a programming language, a component is managed as a single compilation and deployment unit. In java, a component is released in its own jar file. The Object Oriented Models and UML diagrams etc. are designed and managed at component level. There are separate build tools like maven, ant etc. (And IDEs like JBuilder, Eclipse etc.) which allow you to work with separate components as separate compilation units.
Another requirement for a component is independently testability. A component should be independently testable (provided that the components, on which it depends, are already tested and released) and should not be depending upon components on which it is not directly dependent.
If a component is isolated from the effects of other components, then it is easier to trace a problem to the fault that caused it and to limit the damage the fault causes. It is also easier to maintain the system, since changes to an isolated component do not affect other components. And it is easier to see where vulnerabilities may lie if the component is isolated. We call this isolation encapsulation.
Information hiding is another characteristic of modular software. When information is hidden, each component hides its precise implementation or some other design decision from the others. Thus, when a change is needed, the overall design can remain intact while only the necessary changes are made to particular components.
I read somewhere that Encapsulation is the "technique for packaging the information [inside a component] in such a way as to hide what should be hidden and make visible what is intended to be visible."
In good software, design and program units should be only as large as needed to perform their required functions. There are several advantages to having small, independent components.
  • MaintenanceIf a component implements a single functionality, it can be replaced easily with a revised one if necessary. The new component may be needed because of a change in requirements, hardware, or environment. Sometimes the replacement is an enhancement, using a smaller, faster, more correct, or otherwise better component implementation. The interfaces between this component and the remainder of the design or code are few and well described, so the effects of the replacement are evident.
  • UnderstandabilityA system composed of many small components is usually easier to comprehend than one large, unstructured block of code.
  • ReuseComponents developed for one purpose can often be reused in other systems. Reuse of correct, existing design and code of components can significantly reduce the difficulty of implementation and testing of new systems.
  • CorrectnessA failure can be quickly traced to its cause if the components perform only one task each.
  • TestingA single component with well-defined inputs, output, and function can be tested exhaustively by itself, without concern for its effects on other modules (other than the expected function and output, of course).
  • Salability. A component or some components in aggregation (not the whole product) can be used to create a new product or may be used to solve a different problem and thus may be sold out individually, therefore increasing our customer domain. Thus creating/extracting components which are independently sellable out of a system can increase our revenue and may prevent the rework required to do for new software.
A modular component usually has high cohesion and low coupling. By cohesion, we mean that all the elements of a component have a logical and functional reason for being there; every aspect of the component is tied to the component's single purpose. A highly cohesive component has a high degree of focus on the purpose; a low degree of cohesion means that the component's contents are an unrelated jumble of actions, often put together because of time-dependencies or convenience.
Coupling refers to the degree with which a component depends on other components in the system. Thus, low or loose coupling is better than high or tight coupling, because the loosely coupled components are free from unwitting interference from other components.
(The above portion of this blog article is noted from the article at:http://www.phptr.com/articles/article.asp?p=31782&seqNum=5&rl=1)
Compile time dependency on another module is the highest degree of coupling. If a component is dependent on another system, it must be dependent only on the component’s public API (interfaces) at the compile time. It must not care about the implementing classes of the interfaces and the implementations should be made available at the runtime by the runtime environment through dependency injection or by the code written to integrate the system.
Coupling of components should be well thought upon. And a component should use only as less part of another component as possible so that the effect of changes in other components is minimized on the work of our component.
We should never copy paste piece of code or a method etc. because copied and pasted code means copied and pasted bugs and a possibility of getting the bugs not fixed everywhere the copy-paste process has propagated them.
Usually the component design is accomplished by senior developers which have much experience and understand the benefits and losses of less and excess modularization. But the implementation of the components is designed by novices and therefore poor code is generated which gets poorer as the time elapses because of changes made by various persons (It is a fact of I.T. industry that code is seldom maintained by its creator). There must be some general practice while working inside a well defined module so that the code generated is understandable, simple, and maintainable and maximum reused.
In general we can follow following guidelines while coding to keep our code clean and maintainable:
  1. If a class has a lot of public methods and the class may be extended by another class, then an abstract super class and/or an interface should be extracted from the class.
  2. If a class has some static methods which are also being called from outside of the class and the purpose of the class is not to hold those static methods only, then a new utility type class should be created as much above in the component hierarchy as possible, and all the static methods should be shifted to that utility type class. The constructor of the utility class may be kept private and all the other classes may call the static methods by using the utility class name or by importing statically the methods of the utility class (static import facility is available in Java 5.0). This pattern is also known as static class pattern.
  3. If a class has methods that perform some general work but also contain code to locate some resources etc. also (like a method to perform database query also contains code to create database connection or a method to publish to a JMS topic also contains code to look up the destination) then the resources required should be accepted as parameters to the method and the code to look up such resources should be shifted to places from where those methods are being called. This would make those methods more general and in most cases you will find that such methods, which were an instance method before such refactoring, can be made static now. Also if the no. of such methods grows inside a component then they can be moved to a utility type class. Also utility class may be broken in several classes according to the classification of utility methods which are contained in it.
  4. Instead of copying and pasting code from one class to another, you will find that the code may be reused by refactoring the existing classes and using inheritance or composition or by creating utility classes. Never copy-paste code by deferring such refactoring for the sake of pressure and priority of work at hand or only because you are in haste. When the complexity and size of classes grows, the refactoring of the code becomes more difficult and sometimes impossible (impossible in the means that creating the component from scratch seems more economic than refactoring the existing one).
  5. When you are coding, do not use small variable names like a, b, c, x, y, in, out, index etc. and use the names from the problem context instead. E.g. chargeAmount, chargedQuantity, orderIndex etc.
  6. Use code inspection tools like PMD (http://pmd.sourceforge.net), FindBugs (http://findbugs.sourceforge.net) and Lint etc. The errors and warnings reported by these tools and the best coding practices against each error/warning described in the documentation of such tools will give you better idea of good and bad coding.
  7. Do not be afraid of refactoring. Use built in facility for refactoring in your IDE as much as possible. If you have to refactor a large piece of code, then you may use tools like RefactorIt, etc. (which may be available as freeware/shareware/trialware or commercial).
  8. When ever you find that a process which has a definite input, definite output and a definite process to carry out the result is embedded inside a piece of code which is intended for something else (and using this as a part) then this should be extracted in a separate method/class/component as applicable.
The use of refactoring tools and code inspection tools regularly on your code will improve the quality of the code you produce and apply the rules of modularization to a greater extent.

Some comments from my old blogs :--

Anonymous said...
You forgot to give credit to where much of this article was taken from.

No comments: