Coding Conventions for Lodju
Lars
Wirzenius
liw@iki.fi
2001
Lars Wirzenius
Legal notice
This is based on a similar document originally written for
my employer, Akumiitti Telematcis Ltd. I have received permission
to use the text to make this free version.
1
November 1, 2001
Lars Wirzenius
Initial (free) version.
2
December 2, 2001
Lars Wirzenius
Renamed. Added paragraph on gettext.
Abstract
This document defines the coding conventions used in the
Lodju project.
Introduction
This document defines the coding conventions used in the
Lodju project.
When applying these conventions, sound engineering practices
must be followed. Using common sense guided by experience is
necessary.
This document does not strive to give all relevant
information. The "References" section contains useful references,
which should be familiar to all programmers hacking C.
Coding style
Code is formatted approximately according to the style
in Kernighan and Ritchie, "The C Programming Language", second
edition. Indentation size is four spaces, tabs are eight spaces.
Code that looks messy is to be fixed.
Functions are named with lower case letters and digits
and use underscores for word delimiters: keyboard_init,
event_unregister_handler.
Types are named with each word beginning with an upper case
letter and without word delimiters: Event, BinString.
Macros are named using upper case letters, digits, and with
words separated with underscores: EVENT_GET, INTEGER.
Constants can be defined with macros or with enum. In the
latter case they are named as if they were macros: EVENT_TYPE_COUNT,
TIMER_TOTAL_COUNT.
Each source file (.c) shall use a prefix for all names it
exports: functions, types, constants, macros. No deviations from
this will be allowed. The prefix is not case sensitive and is
formatted for each name to fit the type of the name. For example,
photodb.h uses loop the prefix "photo", so functions are called
"photo_create", "photo_db_create" and so on, the types are called
"Photo" and "PhotoDB".
Names not visible outside a module do not need to be prefixed.
Functions and variables private to a module must be static.
Variables global to the whole program are not allowed.
Function return values should follow the Unix system call
convention: if the value is an integer, -1 indicates error and 0
indicates success. If the function returns not just a sucess or
failure indication, it can use non-negative values to indicate
various kinds of success. For example, a function to read a list
of values from a file could return -1 for error, 0 for no items
read (empty list), or a positive value giving the number of items
read. Similarly, a function that returns a pointer value shall return
NULL to indicate failure, and a valid pointer (e.g., to the list of
items the function has read) otherwise.
When any function is called that can fail, its success must be
checked. If the failure of the called function need not be handled,
for example, when printing a panic message immediately before
rebooting the terminal, this must be documented in the code by
casting the return value to the void type, and/or in a comment:
/*
* If we can tell someone we're panicking, that's nice, but
* it's not all that important. Therefore, we don't care
* whether the following printf succeeds or not.
*/
(void) printf("PANIC: Rebooting immediately\n");
The typecast above tells the compiler that we intentionally do not use
the return value of the function. This way, unhandled return values can,
with suitable tools, be found automatically.
Each file shall start with a file comment that gives the
name and purpose of the file. For header files, these should be
directed at the users of the files, not the implementors of the
corresponding modules. The header file comment should explain
how the module or modules are used, not how they are implemented.
The comment at the beginning of a .c file is intended for those
understanding the implementation, such as those who need to modify
or fix the module. Example file comment:
/*
* list.h - simple list abstraction
*
* This module implementes a simple list abstraction. A
* `List' can contain zero or more integer item. Items can
* be inserted and deleted at any point in the list, and the
* list can be indexed. It grows and shrinks dynamically as
* it is used.
*/
Each exported function shall have a (possibly brief) comment
in a suitable header file that explains what the function does,
what its arguments are, and what it returns. This comment must
not explain how the function is implemented. If such a description is
necessary, it can be described in a function comment in the module
that implements the function. Interface comments are not duplicated
at the implementation. Example function comment:
/*
* Delete an item from the list, and return the new length
* of the list. This function cannot fail.
*/
int list_delete(List *list, int item);
When many functions share the same characteristics, such as
how a list numbers items, these can be explained once in the header
file comment.
Exported data types and macros should be commented in the
header file similarly to functions.
Integer types should be used by using their native names
according to the guidelines proposed in the C FAQ, question 1. Aliases
for the builtin types provided by GLib shall be used, but not the
names that require specific sized of types: if there is a need for
an unsigned type that is at least 16 bits, "unsigned int" or "guint"
works well for this. Code that requires exact sizes of basic types
should be avoided.
Note that a plain char type should mostly be avoided, since
it can be signed or unsigned at the compiler's discretion and this
often causes bugs that are hard to track.
#if, #ifdef, and variants should be avoided except in the
following circumstances:
To temporarily comment out code. Such code should
not be commited to CVS.
To conditionally include a header depending on
the target platform and configuration.
Note that including or excluding a complete .c file is to be done at
the makefile (or similar) level, not with the C preprocessor.
Avoid unnecessary typecasts. Typecasts tend to be a signal
that you're doing something complicated, error prone and probably
unportable. Sometimes this is necessary, but usually it's just bad
coding.
Data types should be defined as abstract ones, with all
manipulation of them via functions (or, at most, via function like
macros). The data type should, when possible, be defined as an
incomplete struct in the header file:
typedef struct List List;
This makes the compiler aware of the type called "struct List",
but does not tell it how it is defined. Thus, it is impossible to
do anything with it except use pointers to it. This makes it easy
to enforce that the abstractness of the data type is not violated
by manipulating it directly and requires users of the data type to
go through the function interface. The module where the functions
are defined then declares the "struct List" type properly.
When an object of an abstract data type is created and
destroyed, it should be done via functions named *_create and
*_destroy. This convention makes things systematic and makes it
easier to maintain the program.
A change log is kept in the file ChangeLog at the root
of the source tree. Change log entries should be written to be
understandable without reading the corresponding patch. Every commit
must be documented.
GNU gettext is used to
translate all user visible text to the user's preferred
language. Every file that has translatable strings is
to be listed in unitlabels macro in the
Makefile. Every translated string in executed
code must be used through the _ (underscore) macro:
_("Hello, world.")
This macro will look up a translation or, if not found, use the
argument directly. Strings that need to be translated, but is not
embedded in executable code, must be marked in the source with the
N_ macro to allow the gettext tools to extract
the text for translation. The actual translation must still be done
via the _ macro.
Unit testing
The code uses a simple unit testing framework to
ensure code quality. See for example photodb.c. Each module with unit
tests adds them at the end of the file.
There must be at least three functions:
static void setup(void);
static void teardown(void);
int MODULEPREFIX_unit_test(void);
'setup' sets up each unit test, 'teardown' restores things to a known
situation after the test. These might, for example, initialize and
destroy some dynamic variables used by the tests.
The tests themselves are written into the function
'MODULEPREFIX_unit_test' (where 'MODULEPREFIX' depends on the module,
e.g., 'binstring'). This is the function that is called by the unit
tester main program. Each test inside the _unit_test function should
be written using the TEST and ASSERT macros (defined in termpriv.h):
TEST("dummy test",
{
ASSERT(1);
});
The TEST macro gets the actual test as its argument (together with
a short "name" for the test, used in progress reports). The ASSERT
macro gets a condition as its argument, and reports failure to the
unit tester main program if the condition is not met.
The main program is in the test-run.c file, and whenever a new
module with unit tests is added, test-run.c needs to be modified
(see the source).
See http://junit.sourceforge.net/doc/testinfected/testing.htm
for the article that inspired this implementation.
References
This section contains references to other sources of information
about the C language. Familiarity with them is mandatory.
"The C Programming Language", second edition, Brian
W. Kernighan, Dennis M. Ritchie.
"comp.lang.c Frequently Asked Questions", Steve Summit,
http://www.eskimo.com/~scs/C-faq/top.html