Design adds value faster than it adds cost --Joel Spolsky
Design has always been the most critical and prominent phase of a software development process. A good design not only ensures smooth implementation but also provides enough flexibility to accommodate future changes.
The aim of this article is to provide some useful tips which can help confirming a good design. This article shouldn’t be read as a tutorial to master the design or deeply understand every aspects of design, instead it should be used as a reference to confirm the wellness of your design. The article is structured with tips for good design along with few questions in each section which confirms whether the design follows the underlying principles or not. This unique way makes it easy to use this document as a final check list to affirm the quality of the design.
One of the first goals towards a good design is to keep the modules of the application as logically divided units, each corresponding to a specific business need. The system should be designed in the way that the logical separation between modules is maintained in conjunction with the re-usability of the common logic and code. In large applications modularization become more prominent and unavoidable.
To confirm whether your design is modular or not, answer yourself the following questions:
If answers to these questions are ‘yes’ or 'nearly yes', your design is modular in nature.
Reusability of the design, code and logic is one of the salient measures of a good design. The increased reusability of the code assures centralization of logic and hence lesser efforts towards changes.
To confirm whether your design has reusability feature, you can ask following question to yourself:
If answers to these questions are ‘yes’ or 'nearly yes', your design is modular in nature.
- Have we used the same design to accomplish similar business functionalities?
- Have we designed so as to reuse the same code for the similar business or application logic? OR Is our logic is centric and reused at multiple places wherever required for e.g. central logic for logging,/auditing error handling, localization and so on?
- Have we used Abstraction, Inheritance and factory pattern, wherever applicable, to ensure reusability ?
If answers to these questions are ‘yes’ then you are definitely in the category of reusable design.
3. Loose coupling
Loose coupling, in broad way, indicates that the two components/tiers/layers, while communicating with each other, are not impacted with changes in other provided the communication protocol is not changed. Loose coupling can even be extended to the situations where the two components/layers/tiers lie in different technologies, languages or platforms. Interface based design in object oriented programming languages and Service Oriented Architecture are few examples where loose coupling is implemented to ensure these characteristics.
To confirm whether your design is loosely coupled, ask following following questions:
- If we change the implementation of a particular component or tier (web, business, persistent), the other components or tiers in the system don't need any change?
- If we choose different technology or platform for one tier, it doesn't require any internal changes in other tiers (except the code responsible for establishing communication channel for e.g. service locator)?
- Have we used ‘interface based programming’, business delegate and service locator patterns to ensure the loose coupling in code?
Positive answers to these questions indicate loose coupling of design.
‘Change’, as we all know, is an unavoidable and the most expected happening in software life cycle. The change could be in business requirements, underlying technology/API/tools, or even in design/architecture. Although being stated as the most expected thing to happen in software life cycle, designer has to take a judicious approach to provide flexibility for changes in design, so that neither it is too much flexible to anticipate huge changes nor it should be too arduous to accept any change in future. Many times, domain and business understanding of the product, discussion with product management team and thorough product understanding can help in taking this decision.
To confirm whether the design is changeable, the designer can ask himself the following questions:
- Will it take least possible effort to accommodate a feasible change in business or technical requirements of the application?
- Have we kept the logic of (anticipated) changeable business functionality at a center location so that changes here resulted in least impact on rest of the code?
- Is our design loosely coupled with the frameworks, 3rd party API, containers, servers and other outer entities, so that changes in any of those don’t impact our design?
If answers to these questions are ‘yes’, your design also has 'changeable' feature.
5. Scalability and Plug-ability
A good design provides scalability with ease. The term scalability, in general sense, indicates the capability of the system to perform well in the situations of high data volume or users or service requests. A good design should always be done keeping in mind the scalability aspect of the application. A slightly related feature is functional/technical extendibility, which indicates the capability to add new application functionality or a technical features with ease. An example of technical extendability could be a good I18N framework, which uses .properties file as resource bundle but provided with felxibility to extend code to consider XML or txt files as resource bundles in future.
On the similar line, plug-ability allows adding new functionalities or features in the system on the lines of already existing features for e.g. a good web application could be designed in such a way that a new module could be added by adding a jar file and minimal configuration.
To confirm whether the design is Scalable and Pluggable, the designer can ask himself following questions:
- Have I used polymorphism and other relevant practice to make sure that the use of functionality is not tightly couple with its implementation, so that I can provide room for scalability of the code?
- Have I provided sufficient API for the functionalities to be extended?
- Have I provided room in the design to incorporate other features as pluggable in the system?
If answers to these questions are ‘yes’ then the designer can affirm that the design is scalable and pluggable.
6. Robustness and Stability
A robust and stable design ensures that the system won’t crash or fail in any condition whether it is favorable or disastrous. In other words, the system will handle all inputs gracefully whether in correct or incorrect format.
To confirm whether the design is robust and stable or not, the designer can ask following questions himself:
- Have I handled all possible erroneous conditions in the system?
- Have I designed comprehensive test cases to make sure that the system is unit tested against all possible input information?
If answers to these questions are ‘yes’ then the designer can affirm that the design is robust and stable.
One of the biggest challenges in design is to make sure that the system will run equally good in heterogeneous environment by simply changing the configuration accordingly. Providing configurability in design means externalization of static and external information (may be in some xml file) so that the same can be changed without the need of any code change or even re-deployment in best cases. For an example, in an application requiring DB connectivity, the information like DB URL, driver class name, user/password etc can be externalized to some XML or text file so that it can be changed at runtime without the need to redeploying the application or any code change.
To confirm whether the design is configurable or not, the designer can ask following questions himself:
- Have I externalize all static and external information in the system?
- Does any change in the external information like DB, JDK version, XML Parser/Transformer/Data Binder, log and audit file etc can be accommodate in the system without any code change?
- Is the mechanism for gathering external information like GUI or XML file, is capable enough of holding complete required information?
If answers to these questions are ‘yes’ then the designer can affirm that the design is configurable.
The last but not the least is testability feature in the design. Unit testing has become one of the basic requirements for the success of the software. The design should provide room to accommodate testability in the same. As the word explains itself, testability feature makes the environment easy to plan, compose and execution of test cases to test all functionalities at unit level.
To confirm whether the design is testable or not, the designer can ask following questions himself:
- Is the design provides easy environment to execute test cases to test all features in the application at unit level?
- Is the design compatible with the other unit testing framework like JUnit?
If answers to these questions are ‘yes’ then the designer can affirm that the design is testable.
An important point to note at the last is that, although categorized in separate sections, the above principals are closely related and dependent on each other. For e.g. the reusability not only avoids code duplication but also increases the changeability in the system. Similarly testability increases the robustness and stability of the design. Hence the principals are tightly coupled with each other and should be considered as complement of each other.
In the last I would like to quote Sir Kevin Mullet’s great statement:
The most powerful designs are always the result of a continuous process of simplification and refinement.