Module design which is also called "low level design" has to consider the programming language which shall be used for implementation. This will determine the kind of interfaces you can use and a number of other subjects. On these pages I want focus on module design for the C programming language and show some crucial principles for a successful design, which are the following:
Object orientation is nowadays usually assiciated with certain design methods like UML and programming languages like C++ or Java. However, the principles of object orientation were developed long before these methods and programming languages were invented.
The first steps of object oriented design were done by using C. And indeed object orientation is a principle of design rather than a tool or method based feature. Some of these principles are:
Orientation of program components at the real physical world. This means that the division of
a software package is done according to the real outside world of a system and according to main internal tasks of such a system.
Combining of all elements of a software (i.e. data, definitions and procedures) into an object.
This means that everything that is needed to handle an element of the system is grouped and contained in one object. This is called encapsulation and will be further elaborated.
- Access to the object's data and functions via a clearly defined narrow interface and
hiding of elements which are not required for the outside world. Example:
Encapsulation and Information Hiding
The principle of encapsulation goes hand in hand with "information hiding" and is part of the idea of object orientation. The principle is that only the data which are part of the interface of an object are visible to the outside world.
Preferably these data are only available via function calls, rather than being presented as global variables. An encapsulated module design (related to C-programs) can be achieved by:
The use of local variables inside the functions as far as possible. I.e. avoid variables with validity across functions or even modules.
The use of C-function interfaces i.e. pass parameters and return parameters for data exchange, rather than global or static variables.
If values have to have a lifetime bejond one execution loop, use static variables rather than global variables.
Design your software with a synchronized control and data flow as outlined below.
The advantages of encapsulation are at hand:
No interference form other software parts. I.e. global variables are not available to the outside world and thus no other software portion can access them to modify them.
No unexpected results for users of an object. Accessing global variables inside another object for reading may give you unexpected behaviour of these values. Unless you fully understand and consider the interiors of another object you never can be sure if it is behaving as expected. Clearly defined interfaces can be tested and documented regarding their behaviour. Some global value from in between a module may give you surprises.
Good testability of the individual components.
Good maintainability because of clearly defined behaviour and interfaces.
Reduced resource consumption! Inexperienced programmers don't believe this, but a couple of design improvements we did in the past clearly confirmed it again and again.
Runtime is optimized in many cases by up to 40%. If the outlined design principles are followed consequently values are kept in CPU registers rather than in memory. The access to registers is usually much faster than a memory access. RAM is thus optimized by 30-40% as well, since variables are in registers or on the dynamic stack rather than on fixed locations in the memory.
These advantages are much bigger than the trade off i.e. the execution time and stack consumption for calling a couple of additional functions.
Synchronizing of Control and Data Flow
A picture tells more than thousand words in this case. Below you see a part of a typical design of a microcontroller software. This is done the "classical" way i.e. in an "open" design using global variables and not
using the interfaces of the C-functions.
Tthis is the way a lot of microcontroller software is still done today. As you can see there are mainly global variables, and they are accessed form various functions. Can you imagine what happens if the sequence of the function calls is modified? This code is hard to maintain and even harder to test and may have a lot of surprises for you, up to field re-calls of your products. A surprise you certainly don't want to have!
In this picture you can see basically the same software following the principles of encapsulation and synchronized control and data flow:
How you can achieve this?
Follow the ideas of object orientation.
Make use of the encapsulation principles as outlined above.
Use the C-function interfaces and - use only C-functions as interfaces.
Make your system data flow driven. This means that e.g. if you expect an output of your system you should call the output generating function. This function needs data to make decisions and this data it gets by calling other functions in turn which e.g. do calculations and evaluation of intermediate results. The intermediate results are aquired by again calling other functions which aquire and prepare peripheral inputs e.g. sensors, etc.. Of course there are still a lot of things to consider even is a design as we just outlined, but in the end it will be stable and understandable, and even resource saving.