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