10 software system's design principles
My participation to software realization projects often leads me up to use or to remind one some fundamental design principles. I wished through this article share my vision of such an activity. These principles are actually quite generic and can often be applied whatever the targeted object's abstraction level is (applicative architecture, technical architecture, Java or Ruby implementation, etc.). You will find, of course, object paradigm basis principles as well as other ones more general and pragmatic.
I try to summarize each of these principles with a simple sentence at the present imperative with only a verb and an object complement. This make these rules short and easily remembered. They can be the basis for a designer/developer mantra :-)
"Encapsulate the implementation"
This basis principle from object paradigm, also called Information hiding":http://en.wikipedia.org/wiki/Information_hiding , involve a net parting between the software component's user interface and its implementation. The latter must be completely inaccessible to the component user. I use the term "software component" for it can takes various form : a function, an object, etc. The "interface" principle use, like in Java, enable to physically separate these two elements.
"Separate concerns"
Fundamental principle allowing modularity in a system by clearly separating the roles hold by each components of the system. This separation can be arbitratry but nervertheless we can identify some metrics allowing to determinate bad balancing of responsability in a system (high number of classes in a packages, high number of lines in a function, high number of operations in a class).
We can lead to an efficient identification of responsability, especially in object oriented design by using technics such as CRC cards . Within an application we often find at least technical concerns separation between the business functions and the GUI with the MVC pattern.
"Minimize spatial, temporal and structural dependencies"
Principle, in my opinion, extremely fundamental that leads to clearly define what are a component's dependencies. For each dependency, we will try to determine the different quality described righ after. The objective is, for each dependency, to minimize it and then to apply the principle of loose coupling .
- The spatial dependency is concerned with the components location. The use of naming service (DNS, JNDI, etc.) or a registry permits its reducing. The simplest mechanism stay, in my opinion, the URI. This leads again to the REST integration (see my previous post on this subject ).
- The temporal dependency is concerned with the synchronism and asynchronism concepts . A system will be less dependant to another system if it exchanges with it in an asynchronous way as the availability of a component in dependency at any give time is not necessary anymore. This do not imply that our system is fault tolerant because it is now dependant on a message queue , nevertheless the reliability of this technical dependency is easily handled by mechanism such as"Store-and-Forward":http://en.wikipedia.org/wiki/Store_and_forward (then we talk about Reliable Messaging). In order to clearly represent yourself the asynchronism notion and the two-phase commit one needed by synchronous interaction here is a link towards an article by Gregor Hope on asynchronism .
- The structural dependency deals with data structure needed by an interaction as well as invocation interfaces (operations signature to be simpler). The more rigid the structure will be, the more dependant our two systems will be. It is then better to forget binary data structure and going towards XML data format and their large possibilities (XPath, XQuery, XML Schema, etc.). The structural dependency leads to the versioning concern that must solve the problem of servicing severals versions of a service to several different clients.
"Reduce states"
This principle is for me the basis one allowing error causes decreasing by reducing the number of concurrent states in a system at a given time. Ideally, state storages part of a software system can be reduced to the execution stack and the database. We also often find a third one called "Session" in web development, notably in Java nd .Net, but not present in PHP in its default programming model.
This point is a consequence of the functional programming paradigm, that is the removal of state variables by non imperative programming. This article clearly explains why "pure" functional languages have few concurrents states from their initial design.
"Fail fast"
The Fail-fast principle is associated with the Verify the assumption principle. When processing's assumption or requirements are not fulfilled (parameter is missing, error when using another component) the component have to fail in the most explicit and incontrovertible way, with an exception for example. When a process does not run successfully the error must be reported immediatly. The error can also implement the "Give meanings" principle : give, if possible, the root cause of error, its description and moreover the actions leading to its fixing.
"Give meanings"
This principle has the objective of giving the most readable semantic on every naming (variable, class, directory, component, etc.). The code or the environment is then auto-significant and don't need added signification elements in the form of documentation. If possible, like the javadoc tool with the java platform, the code should include its documentation. The naming and the signification refine as one goes along the realization, it is then necessary to be able to easily do source code modification with the help of refactoring features.
"Check assumptions"
If an assumption checked by a component is not fulfilled the rule Fail fast should be applied. This principle is a consequence of the Minimize the dependencies principle, each time that a dependency is necessary its validity should be checked at runtime. Each time a developer says to himself "such a component has to give me such elements" (such data or a data formatted in a particular manner) the component has to verify and throw an error in case of a condition not fulfilled. A programming technique is associated to this principle : Programming by contract . This programming technique is also quite close to Defensive programming even if this one firstly target a better component's security level
"Parameterize by convention"
Each component needs parameters likely to modify its behavior. Each of these parameters need a default value or can deduce their behavior from the naming of another component. A classical example in a MVC framework : the forward towards a view named "myView" will be used by the framework that will find the file "myView.jsp" in its runtime directory. The convention parameterization avoid the burst and increase of a system parameters settings.
"Protect the variations"
This principle is used when some clients depend on the built component. It implies to anticipate the interface modifications in order to not broke the component use. The information on the interface obsolescence is yet pertinent, this is for example the "deprecated" functionnality provides by the Java language and its javadoc tool. This principle is also linked to versioning, which is a entire subject by itself and that will be the object of another post. This principle is also often associated to the one denominated Open-Closed .
"Design extensible and adaptable"
This principle is quite general and allow respectively to improve the built component. Theses principles are not, of course, indispensable, notably the extensibility one that can be quite costly to implement. Nevertheless the adaptability principle is quite easy going and can bring a great evolutivity.
- The extensibility can be provided by implementing mechanism like plugins that we find in richly featured software (Eclipse, Photoshop, etc.). Inheritance is a finer extension mechanism that find its origin in the object paradigm
- The adaptability implies to identify the component's variable elements and to extract them as a parameter of the component. The interface notion or the closure one in functional language allow a component's user to provide the implementation that this one will use and so adapt the behavior not with parameters data but with operations. It's also one of the strength of the dependency injection frameworks, like Spring in Java, to offer interfaces and implementation with simple parameterization and then offer an excellent adaptability. In functional language like ruby or python, the function passing parameters or the yield feature allows a finer management of this specific code to be inserted.
Conclusion
My post about programming languages concluded with the importance that must be given to the design activity. This post humbly tempt to deliver some general software system's design principles that I could experiment and appreciate their power. In order to complete this article it is interesting to think about an automatic way of verifying all these points.
