Modularity is an important key for code maintainability and handling complexity. If you introduce a module abstraction layer on top of the functions, you can decrease problem complexity tremendously23.
Interfaces
Interfaces are the declarations used by implementation and referrers. They consist of type definitions, function declarations (prototypes) and maybe global data declarations24 and are located in header files.
No extern declaration must appear in C files. They belong in commonly included header files.
I tend to design one header file for each C source file. If several modules are linked together to a library, a new external header file may be created that declares all extern functions. Alternatively, all of the extern functionality can be encapsulated in one source file25. Its according header then becomes the external header.
There are very few reasons to include static definitions in header files.
There is in my opinion no reason to include static function declarations in header files. They belong in the source file that implements the functions.
Header files
Header files that are intern to a software component should be included with #include "header.h", header files that are extern to the component should be included with #include
Header files must be included by both the implementing module and the calling module(s). Note that the inclusion in the implementing module is not enforced by the compiler and the linker will link independently of actual function parameter types26.
As mentioned above, it's wrong to declare static functions in a header file or to not define functions as static that are only used in one module.
Header files should use include guards to enable27 (intended or unintended) multiple inclusion28:
#ifndef header_h
#define header_h
...
#endif
Header files must not be included by an absolute path (e.g. /usr/include/header.h), paths can be specified in compiler options (-L/usr/include). Using relative paths is correct (e.g. sys/header.h), however using .. seems confusing (e.g. ../header.h).
Never use the extern keyword in source files, use it only in header files.
Resources
Resources are data-only modules. Samples are X11 bitmap files.
Resource data should carry the const modifier, so it can possibly be put into a read-only data section29. This allows instances to share the resource memory and the operating system does not need to allocate virtual memory paging space for it.
Code order
There are two traditional styles of code order (i.e. order of functions in a source file). Pascal style defines a function before it is referenced. C style defines a function after it is referenced.30
There are some aspects to consider about the order of the code in a source file:
• Using Pascal style makes it unnecessary to track function interface changes in the prototypes.
• A compiler may chose only to inline functions on high optimization levels and if they have been defined before.
• C style sources seem to be easier to read since going from the start of the file to the end is much more like the actual program flow compared to Pascal style.
Error handling should typically be done without delay, e.g.
fp = fopen(file, "r");
if(fp == 0) return -1;
process(fp);
is in my opinion preferable to
fp = fopen(file, "r");
if(fp != 0) process(fp);
else return -1;
It is more readable since it doesn't add one layer of indentation per condition and the 'process' part above may be large and tear the blocks quite apart.
Conditional compiling
The opinions about conditional compiling differ. Programmers either
• don't use conditional compiling at all
• conditionally include only a few macros and include directives
• conditionally include large parts of code
due to the fact that conditional compiling seems hard to read to some.
Nested conditional compiles seem to confuse further.
However, suitable editors can display uncompiled parts of the code in comment font type31 and even fold it, to make sources more readable. There are tools available to remove the unused conditional parts32.
Alternatives to conditional compiling are including one of different header files (with the -I compiler option) or linking one of different libraries (with the -L compiler option) or both. In development, you may chose to create a set of subprojects and include and link one of them.
Conditional use of parts of code, header files, libraries or subprojects is often used to encapsulate platform specifics.
Code nesting
Block constructs should not be nested to deeply. Nested loops tend to get hard to read.
You may want to design a separate local function that implements the inner part of a loop, to avoid deep nesting. This may make it easier for a compiler to optimize the code33.
I would say that no more than two levels of nesting should be used.
Designing small functions and using return on error cases instead of adding a layer of if/else further reduces nesting and makes code more readable (in my opinion).
Scope
The scope of an identifier (function, type, etc.) is the part of the code in which the identifier can be referenced. The C programming language offers
• application scope
• file scope
• function scope
• block scope
(ordered from broad to narrow).
Choosing the scope of a function, variable or type is one of the most important micro architecture instruments.
Generally, scope should be chosen as narrow as possible.
Scope of functions
Functions have file scope (static) or application scope (not static). Limit a function to file scope if possible. This affects the modularization of a software component (i.e. the way functions are grouped together to source files).
A narrow function scope encapsulates code.34
Scope of variables
Variables can have all four kinds of scope: application scope, file scope, function scope, block scope. The variable scope should be as small as possible.35 The opinions about using variable declarations in block scope differ.
Typically, there is no overhead (stack pointer operations) involved with block scope variables, space for the deepest possible block allocation gets reserved at function entry.
Application scope leads to global variables, which generally should be avoided. Use functions to access the data (getX() and setX()) instead36.
Application scope or file scope (as well as static data in functions) lets the functions that access the data only be useable by a single thread37.
A reference to a calling functions buffer should be used instead of global or static data to avoid multithreading problems and buffer overwrite problems (e.g. as with localtime()).
Friday, January 9, 2009
Modularity
Posted by abhilash at 8:55 AM
Subscribe to:
Post Comments (Atom)


0 Comments:
Post a Comment