|
|
Module Design
Module design which is also called "low level design" has to consider the progamming language which shall be used in the 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:
Encapsulation
The principle of encapsulation which is sometimes also called "information hiding" is part of the idea of object orientation (see high level design). 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! Unexperienced 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.

Looks like the creepiest piece of hack you have ever seen? Well, sad enough that this 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 and 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. 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.
|
Plan your Module Breakdown and Include Structure
With the above principles in the back of your mind, you have to define your module breakdown. I.e. using an object oriented approach and considering the layers defined in high level design, you have to outline the purpose and contents of each module in your software.
Never leave this up to the individual programmers. The same is true for your include structure. Bad designs have a lot of dependencies and big hierarchies in their includes of header files. About the header structure and their relation to the modules the following
principles have to be followed:
- Make a header for each source code module and in the source code module only include this one header.
- Define sections in your module header which are conditionally compiled and allow to group the function prototypes and data definitions in a public and a private part. The public part consitutes the interface which can be used by other modules.
- Define common header files which are used by all source code modules. E.g. make all you common data type definitions in one header file. Have another header file which contains commonly used macros and definitions.
- Include the common headers separately in each of the module headers.
- Include only these headers of other modules of which you use an interface function. As you include it switch the conditional compilation to only include the visible i.e. public data of the module headers.
- Avoid include hierarchies, nested includes and recursive includes. Ban them! Get rid of everybody in your team who opposes such a clear structure! It will make your life a lot easier.
|
It is our option! You can either have a mess like this picture shows:

By the way this is the include structure of a software as it was detected by an analysis tools, before we worked on it!
Or you can decide for a better solution, adhering to the outlined principles as e.g. this example shows:

|
|