Design Principles, Best Practices and Design Patterns for C (or Procedural Programming in general)? [closed]
Are there any known design principles, best-practices and design patterns that one can follow while designing a C project? Or useful design principles for procedural (imperative) programming in general?
(I'm child of the 'object-oriented generation' and have to design a large C project for the first time)
Information hiding - as espoused by Parnas (Software Fundamentals).
Careful management of headers and visibility:
- Everything in a source file that can be hidden from the outside world should be; only the documented external interface should be exposed.
- Everything that is exposed is declared in a header.
- That header is used where the functionality is needed (and where it is defined).
- The header is self-contained - when you need it, you use it, and you don't have to fret about 'what other headers do I also have to include' because the header ensures it works by including anything it needs to make it work.
-
The header is self-protected - so it does not matter if it is included multiple times.
#ifndef HEADER_H_INCLUDED #define HEADER_H_INCLUDED ...rest of header contents, including other #include lines if necessary #endif /* HEADER_H_INCLUDED */
Design sets of functions to work on 'objects' (usually structures) - and use those functions rather than poking around the innards of the structure in the code that is using it. Think of it as self-imposed encapsulation.
My three advices:
- Write unit tests. They will help you zero in on a design that suites your problem as you go along. Much better than relying (solely) on pre-meditated thinking.
- Have a memory leak detector (there are all sort of libraries out there) installed and running from day one. Have this library print out all leaks as soon as the program/tests exits. This will allow you to catch a leak as soon as you introduce it, thereby making its fixing much less painful.
- Write OOP code in C. Not that difficult. While it is possible to emulate method overriding, I suggest that you start with emulation of simple objects. Even this simple mechanism can give you great mileage.
Here's an example:
typedef struct Vector {
int size;
int limit;
int* ints;
} Vector;
Vector* Vector_new() {
Vector* res = (Vector*) malloc(sizeof(Vector));
res->limit = 10;
res->size = 0;
res->ints = (int*) malloc(sizeof(int) * res.limit);
return res;
}
void Vector_destroy(Vector* v) {
free(v->ints);
free(v);
}
void Vector_add(Vector* v, int n) {
if(v->size == v->limit) {
v->limit = v->limit * 2 + 10;
v->ints = realloc(v->ints, v->limit);
}
v->ints[v->size] = n;
++v->size;
}
int Vector_get(Vector* v, int index) {
if(index >= 0 && index < v->size)
return v->ints[index];
assert false;
}