Coding Rules
These are the rules you must follow when writing code. Those rules reflect the Lib-common philosophy in various ways.
Languages
This document cover all C-like languages when applicable:
-
C
-
C++
-
Javascript
-
Java
-
…
For python
, the written code must pass pylint checks. You can also get
inspiration in "python style as described by PEP 8":
http://www.python.org/dev/peps/pep-0008/, but only the pylint checks are
mandatory.
Basics
-
indentation is made of 4 spaces, no tabs, text width is 78 full columns.
-
Never put more than one statement per line, never put more than one semicolon per line (unless it’s in a string ;p)
do1(); do2(); /* bad */
if (cond) { do_sth(); } /* bad */
printf("a; b; c"); /* good, but better use puts() */
struct foo {
int a; /* good */
char b, c; /* good */
int d; char e; /* bad */
};
-
labels (including switch
case
constructs) are not indented. They stay at the same level as the previous block indent.
int main(void)
{
switch (foo) {
case 1: /* good */
break;
case 2: /* bad */
case 3: /* bad */
goto done;
}
done: /* good */
(void)0;
}
-
One single space between a control flow keyword (such as
if
,switch
,while
, …) and the condition, the bracket is put on the same line (unless it would overflow the columns count).
switch (foo) { /* good */
}
switch(foo) { /* bad */
}
-
Spaces around binary operators:
1 + 3 * (3 << 2); /* good */
1+2*3; /* bad */
-
No spaces before a semicolon or before a comma, but one leading space is mandatory.
fn(1, 2, 3); /* good */
fn(1,2,3); /* bad */
return ; /* bad */
-
Avoid trailing spaces.
-
Never use preincrement when postincrement is equivalent, so that preincrement flags probably tricky code.
++i; /* bad */
i++; /* good */
-
Structure your code in order to keep the indentation level as low as possible.
if else
-
When the condition of a control flow statement spans on multiple lines, keep the boolean operator at the end of the line, and align the next statement on the new line with the first statement. Push the bracket on the next line.
if (test1 && test2 && ... && foo &&
bar)
{
/* do stuff */
}
if (test1 && test2 &&
(test3 || test4 ||
test5))
{
/* do stuff */
}
-
else if
statements are kept on a single line:
/* Good */
if (cond1) {
} else if (cond2) {
} else {
}
/* Bad */
if (cond1) {
} else
if (cond2) {
} else {
}
-
Control flow statements blocks must be enclosed in brackets.
if (foo) { /* good */
}
if (foo) /* bad */
{
}
if (foo) /* bad */
return e_error("bad constraint");
if (foo) { /* good */
return e_error("bad constraint");
}
if (foo) /* bad */
return a_multiline_function_call("some long reason, %s",
some_very_long_variable);
if (foo) { /* good */
return a_multiline_function_call("some long reason, %s",
some_very_long_variable);
}
if (foo) /* bad */
return 0;
else
return 1;
if (foo) { /* good */
return 0;
} else {
return 1;
}
if (test1 && test2 && ... && foo && /* bad */
bar)
return 0;
if (test1 && test2 && ... && foo && /* good */
bar)
{
return 0;
}
Ternary operator
When using the ternary operator on a long line, the colon should be aligned with the question mark (if possible).
a = pretty_long_condition ? another_quite_long_stuff
: something_else; /* bad */
a = pretty_long_condition ? another_quite_long_stuff
: something_else; /* good */
switch case
-
Never put declarations of variable in the main scope of the switch statement
switch (val) {
int a = 1; /* bad; the init is not run */
case A:
a = 0;
break;
}
-
In case the case contains some variable declarations:
-
Put the opening brace on a newline after the last case, at one level of indentation greater than the case labels. Put the closing brace on a newline as well, on the same level of indentation as the opening brace.
-
If a
break
is to be put, put it on a newline after the closing brace. -
continue
andreturn
statements must be kept in the code block since they are not linked to theswitch() case:
construct. -
The code in the block is one-time indented from the
switch
statement
-
void foo(void)
{
switch (val) {
case CONSTANT0:
code;
break;
case CONSTANT1: {
var declarations;
code;
return var;
}
case CONSTANT2: {
var declarations;
code;
} break;
case CONSTANT3:
case CONSTANT4: {
var declarations;
code;
} break;
}
}
-
when a case fall through the following case, this explicit
/* FALLTHROUGH */
comment must be used.
switch (val) {
case A:
code;
case B: /* bad, may be a fall through or a missing break */
code;
break;
}
switch (val) {
case A:
code;
/* FALLTHROUGH */ /* good */
case B:
code;
break;
}
-
Advice: avoid to define the
default
case when doing aswitch/case
on an enum (unless you have hundreds of elements to put in theswitch
). It allows the compiler to display warnings about missing cases whenever the enumeration changed. It applies for IOP unions as well. -
Block rules apply to
switch/case
macros equivalent such asIOP_UNION_SWITCH/IOP_UNION_CASE
. They are not actual labels, so both the case and the code inside the case are indented:
IOP_UNION_SWITCH(val) {
IOP_UNION_CASE(iop__type, val, union_name, v) {
foo();
}
IOP_UNION_DEFAULT() {
bar();
}
}
Functions
-
Opening bracket is on the same line than control flow statements, on the next line for function implementations.
int main(void) /* good */
{
}
int main(void) { /* bad */
}
-
No spaces between a function call and the parenthesis.
fn(1, 2, 3); /* good */
fn (1, 2, 3); /* bad */
-
When the argument list of a function call spans on multiple lines, indent at the opening paren column, unless it’s after the 40th column:
some_call(struct->bar, 19 * long_variable_name, i++,
i_love_long_variables_too_much_for_my_sake);
-
When declaring a function prototype, if the declaration isn’t multiline, we usually put the return type on a single line and then the rest of the prototype. Wrap the prototype arguments following the same convention as function calls.
void something_short(void); /* good */
struct very_long_type_tag *
something_not_very_short(int, struct bar *, int, void *); /* good */
-
Avoid useless wrapping in function prototype and try to keep it on the most limited number of lines
static void
something_not_very_short(struct my_struct *a,
struct my_struct *b); /* bad, could fit on only 2 lines */
static void
something_not_very_short(struct my_struct *a, struct my_struct *b); /* good */
static void something_not_very_short(struct my_struct *a,
struct my_struct *b) /* good */
-
Avoid useless function declarations. A declaration is needed only
-
in case of exported function used in another file
-
in case of mutually recursive functions
-
in case of mutually dependent parts of a same file
-
-
Non-exported functions must be static.
-
Don’t use
inline
andALWAYS_INLINE
modifiers unless this is required. Not doing so will lead to slower compilation and the inability to detect dead code.-
inline
is required when the implementation is put in a header file (see previous point) -
inline/ALWAYS_INLINE
may be required in case a performance bottleneck is detected.
-
-
Always use a verb in function names.
-
When writing a function:
-
when in a module, put what would be
this
in C++ first; -
then come the pure
in
parameters; -
then come the
inout
parameters; -
and finish with the pure
out
ones.
-
-
When writing a function, always prefer prototypes that return an integer and operates on its argument. The immediate win is that writing error management is easier. The second win is that the caller can decide if he wants to allocate memory for the arguments or put them on the stack.
-
When such a function returns a positive value (0 included), then it was successful. When it returns a negative one, then it failed. This follows the usual POSIX and Unix behaviours in the matter.
-
Using boolean returning functions is possible, though the name of the function must contain
is
orhas
, or anything that makes the reader understand that the answer is eitheryes
orno
. -
Using pointer returning functions is possible if and only if the function as a single returned value. In that case,
NULL
must be handled as an error value for further error management.
-
int my_parser_run(const char *s, const char **out, my_struct *s); /* good */
bool can_i_has_more_beer(void); /* good */
object_t *build_object(const char *url); /* good */
bool my_connect(const char *url); /* bad: should really use int */
parser_t *do_parse(const char *s, parser_state_t **state); /* bad: several returned value should be treated the same way */
-
When writing a function that has a single return value that is a pointed object, use that pointer as the return value of the function instead of passing a double-pointer as argument.
-
When writing new string manipulation functions, always use the snprintf semantics, meaning that:
-
first and second argument are a
char *
and anint
that represent the buffer you output to. -
the return type is
int
, and we return the size that could have been used if the output buffer was large enough, not counting the trailing zero. -
the output buffer will always be
NUL
terminated (unless maybe when the function fails).
-
Blocks
-
Don’t write code that use blocks in headers files
-
Declarations that use blocks in headers must use the
BLOCK_CARET
macro instead of a literal caret^
-
Declarations that use blocks in headers must be wrapped in a
#ifdef has_blocks / #endif /* has_blocks */
-
Typedefs of block types must be suffixed by
_b
. -
Functions variants that use blocks instead of callbacks should be named
-
_blk
, unless the block variant is the primary one (use_cb
suffix for callback variants).}) -
Unless impossible or prohibited by performance reason (same block used in several calls), blocks should be inlined in function call:
/* GOOD */
call_with_block(^void (int a, int b) {
});
/* BAD */
blk = ^void (int a, int b) {
};
call_with_block(blk);
/* TOLERATED if call_with_block() performs a Block_copy()
* .. but you should consider refactoring your code */
blk = ^void (int a, int b) {
};
for (int ...) {
call_with_block(blk);
}
-
Inlined blocks should be formatted as follow:
-
keep the opening brace on the same line as the argument list
-
indent the content of the block by one level from the current scope
-
no space after the caret
-
return type get the same formatting as for variable declaration: space after type name, star collated with content.
-
/* GOOD */
call_with_block(^void (int a, int b) {
do_something();
});
call_with_block(^void *(int a, int b) {
return do_something();
});
/* BAD: missing space after type name */
call_with_block(^void(int a, int b) {
do_something();
});
/* BAD: misplaced opening brace */
call_with_block(^void (int a, int b)
{
do_something();
});
/* BAD: wrong indentation */
call_with_block(^void (int a, int b) {
do_something();
});
-
Don’t use inlined block in branching primitives, prefer splitting the branching in two steps:
/* BAD */
if (do_some_call(^{
return do_something();
})
{
}
/* GOOD */
res = do_some_call(^{
return do_something();
});
if (res) {
}
Macros / Preprocessor directives
-
Use a single space between a macro name and its implementation:
#define GOOD 1
#define BAD 2
#define GOOD_FN(a, b) body
#define BAD_FN(a, b) bad_body
-
Function-like macro should be used the same way functions are used: the semi-colon should be in the invocation, not in the macro:
#define BAD_MACRO(_a) do_something(_a);
#define GOOD_MACRO(_a) do_something(_a)
-
When defining block-macros always use the
do { … } while (0)
construction ("see GCC manual": http://gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html):
#define BAD_MACRO(_a) { char *a = (_a); foo(a); bar(a); }
#define GOOD_MACRO(_a) do { char *a = (_a); foo(a); bar(a); } while (0)
-
In multi-line macros
-
the
\
should be aligned right on the 78th colum -
the first
\
can be simply put two spaces after the name of the macro, or one space after the beginning of the body of the macro, in order to keep 2 lines macros simple -
the last line of a macro must not contain a trailing
\
-
the body of the macro must always have at least one level of indentation
-
/* GOOD macros */
#define GOOD_MACRO(a) do { \
do_something(); \
do_something_else(); \
} while (0)
#define GOOD_MACRO(a) do { \
do_something(); \
do_something_else(); \
} while (0)
#define GOOD_MACRO(a) \
do { \
do_something(); \
do_something_else(); \
} while (0)
/* BAD macro: buggy indentation */
#define BAD_MACRO(a) do { \
do_something(); \
do_something_else(); \
} while (0)
/* BAD macro: non-aligned \ */
#define BAD_MACRO(a) do { \
do_something(); \
do_something_else(); \
} while (0)
/* BAD macro: trailing \ */
#define BAD_MACRO(a) do { \
do_something(); \
do_something_else(); \
} while (0) \
-
When creating a
#ifndef
/#ifdef
-#endif
block, put a comment after the endif to repeat the initial condition:
#ifndef MY_HEADER_GUARD
#define MY_HEADER_GUARD
/* ... */
#endif /* MY_HEADER_GUARD */
Or:
#ifndef NDEBUG
#define NDEBUG
/* ... */
#endif /* NDEBUG */
Purpose: for example, avoid confusing a #endif
placed in the end of a file
with a header guard when it is not.
-
When cascading preprocessor
ifdef
,else
,endif
directives and define, use 2 columns per level (including the sharp as a column). Theifdef
guard of a header file doesn’t count for indent levels.
#ifndef MY_HEADER_GUARD
#define MY_HEADER_GUARD
#ifdef __GNUC__
# define foo(a) __builtin_bar(a)
# ifdef __GLIBC__
# define WE_ARE_USING_A_GLIBC
# else
# error "We need a glibc"
# endif /* __GLIBC__*/
#else
# error "go away, loser"
#endif /*__GNUC__ */
#endif /* MY_HEADER_GUARD */
-
Don’t create a macro that don’t simplify the code.
-
Avoid using macro whenever possible. Most of the time, macros can be replaced by functions.
-
Always prefix variables defined inside a macro with the name of the macro (or something derived from the name) to avoid conflicts:
/* BAD macro: __v is too generic and may conflict with another macro. */
#define qv_append(vec, v) \
({ \
typeof(*(vec)->tab) __v = (v); \
*qv_growlen(vec, 1) = (__v); \
})
/* GOOD macro: __qv_v is specific to the qvector module and conflicts should
* only appeared in the module itself which is easy to fix.
*/
#define qv_append(vec, v) \
({ \
typeof(*(vec)->tab) __qv_v = (v); \
*qv_growlen(vec, 1) = (__qv_v); \
})
Variables
-
We do
C99
, use theC99
bool
type, with lower casetrue
andfalse
. -
The Right Type™ for:
-
a string is
(const) char *
; -
a character is
int
(DO NOT EVER USEchar
); -
opaque data is either
void *
orbyte *
. Never EVER usechar *
.
-
-
Never declare variables anywhere but at the start of a new scope. Though using the C99 for loop variable inline declarations is recommended:
for (int i = 0; i < 99; i++) { /* good */
/* ... */
}
-
Add a blank line between variable declarations and code. It is allowed to omit the blank line in case a very short block (two lines: one declaration line, one statement).
t_scope
is considered as a declaration.
{ /* bad */
int foo;
do_something();
do_something2();
}
{ /* good */
int foo;
do_something();
do_something2();
}
{ /* allowed */
int foo;
do_something();
}
{ /* good */
int foo;
do_something();
}
-
Keep a single declaration per line. When no initialization is performed and pointers aren’t mixed with non-pointers variables, the declaration of several variables on the same line is allowed when meaningful (i.e. when all variables are used similarly in the code and you want to emphasize it), but should generally be avoided:
int a = 0, b = 0; /* bad */
int *a, b; /* bad */
int a, b; /* allowed */
int *a, *b; /* allowed */
int *a, /* bad */
*b;
int a; /* good */
int b;
int a, b = 0; /* bad */
my_type_t a, b; /* allowed */
my_type_t a; /* preferred */
my_type_t b;
-
Keep a single line per declaration, unless the variable is const (because in that case the assignation cannot be done after the declaration):
{
int important_variable =
long_function_which_does_not_fit_into_one_line(); /* bad */
}
{
int important_variable;
important_variable = long_function_which_does_not_fit_into_one_line(); /* good */
important_variable =
very_long_assignation_which_does_not_fit_into_one_line(); /* good */
}
{
int variable = long_fonction_with_parameters(parameter1, parameter2,
parameter3); /* bad */
}
{
int variable;
variable = long_fonction_with_parameters(parameter1, parameter2,
parameter3); /* good */
}
{
const int const_variable =
long_function_which_does_not_fit_into_one_line(); /* allowed */
}
-
Stick pointer types stars to the variable names:
const char *var_name; /* good */
int fun(char * foo); /* bad */
char* baz; /* bad */
char *p, *q; /* good */
-
When the deepest type is const, we put
const
front, especially forconst char *
. Though, whenstatic
or any other storage class (extern
e.g.) is used,const
is pushed after the type:
int foo(const char *arg); /* good */
int foo(char const *arg); /* bad */
static const int a = 2; /* bad */
static char const * const str_array[] = { /* good */
}
Types
-
We usually like to use the
_t
suffix for type names, the_f
suffix for functional types and the_b
suffix for blocks types. -
IOP types must end with
__t
suffix when they are modified using@ctype
. -
When declaring an enum, put a traling comma on every line:
enum foo {
FOO_0,
FOO_1,
FOO_MAX, /* good */
};
-
When declaring a type, keep the opening bracket on the same line, then put a mandatory line break. For a
typedef
, keep the type name on the same line as the closing bracket.
enum foo {
FOO_0,
FOO_1,
};
typedef struct bar_t {
/* ... */
} bar_t;
-
When using a
typedef
, always use the same name for thestruct
,enum
orunion
tag and thetypedef
. No tag is okayish though.
typedef enum bar_t {
} bar_t;
enum foo {
FOO_0,
FOO_1,
FOO_MAX, /* good */
};
-
When calling a function pointer, always "dereference" it:
int fun(void (*cb)(int, int, int))
{
(*cb)(1, 2, 3); /* good */
cb(1, 2, 3); /* bad */
}
Constructors/Destructors
-
for every non scalar type
foo
, 4 functions/macros are expected:
foo *foo_new(void);
foo *foo_init(foo *);
void foo_wipe(foo *);
void foo_delete(foo **);
-
if a type is missing one of the previous functions, then the default semantic is:
static inline foo *foo_new(void)
{
return foo_init(p_new_raw(foo, 1));
}
static inline foo *foo_init(foo *v)
{
return p_clear(v, 1);
}
static inline void foo_wipe(foo *v)
{
// deallocate memory allocated in *v such as pointers to other objects
}
static inline void foo_delete(foo **v)
{
if (*v) {
foo_wipe(*v);
p_delete(v);
}
}
-
Optional but possible functions are:
-
foo_reset
: which resets an element of typefoo
as if it was new. By default, iffoo_reset
does not exist, then it may be replaced byfoo_wipe
followed byfoo_init
. -
If
foo_reset
exists, and is needed, it may have a different behavior thanfoo_wipe + foo_init
. -
This can be used to make a value aware of the fact it has been allocated through a memory allocator, or as a static member of another struct. (see
struct wsp_header
for an example).
-
-
foo_delete
should accept a pointer toNULL
as argument -
for containers,
deep_wipe
/deep_delete
take a second argument, that is a pointer to a destructor. If the pointer isNULL
, elements are not deallocated when the container is wiped out or deleted. If the contained type is obvious, the destructor argument may be replaced by a boolean.-
If we pass pointer to functions, it means every type that can be put in an array and that we want to deallocate on array deletion has to have real destructors and not macros. You can consider creating inline functions for those cases, since you can take a pointer to an inline function.
-
Containers should define a
foo_clear
/foo_deep_clear
function that detach the elements from the array, but that doesn’t delete them.
-
Persistent Objects
-
for persistent objects the construction of an object may be done by functions named
foo_create
andfoo_open
:-
the
create
variant creating the persistent ressource and returns the initialised object -
the
open
variant opens the existing ressource and return the initialised object
-
-
for persistent objects, the destruction of an object may be done by functions named
foo_close
andfoo_destroy
:-
the
close
variant closes the ressource and keeps it -
the
destroy
variant closes the ressource and delete it from the persistent storage.
-
-
foo_new
/foo_delete
in that context define the runtime-structure initialization and might only be private,foo_create
/foo_open
/foo_close
/foo_destroy
provide the ressource management API.
Code structuration
-
Programation "topics" can be folded by using the
{{{
and}}}
delimiters (even if it is not mandatory). The name of a fold must be placed after the opening mark{{{
(and should not be repeated when closing the fold). An empty line must be placed after the opening mark and before the closing mark. There should not be any line between an opening mark and a closing mark.
/* {{{ Good fold 1 */
code
code
/* }}} */
/* {{{ Good fold 2 */
code
code
/* }}} */
/* {{{ Bad fold 1 */
code
code
/* }}} */
/* Bad fold 2 {{{ */
code
code
/* }}} Do NOT write "Bad fold 2" here */
-
Nested folds should not be different from the others (no special indentation, no numbering), because it is hardly maintainable:
/* {{{ Fold 1 */
/* {{{ Good nested fold 1.1 */
code
/* }}} */
/* {{{ Bad nested fold 1.2 (bad ident) */
code
/* }}} */
/* {{{2 Bad nested fold 1.3 (numbering is forbidden) */
code
/* }}} */
/* }}} */
-
The code must be placed in
.c/.blk
files. No implementations should be put in the headers unless:-
the function is extremely short and its execution is extremely fast (the cost of calling the function is greater than the cost of executing it)
-
the function only provides an alias for another existing function
-
you can have a real gain from the inliner (some part of the inlined function can be simplified by static analysis)
-
-
Header inclusion should be done at the top of the source file. However, in the case the header is required exclusively for unit testing purpose, it may be included only before the "testing" section of the source file.
-
Header include should be done in the following order:
-
system headers
-
lib-common
-
lib-inet
-
other libs
-
product base includes
-
product module includes
-
local file associated header
-
-
A header should be compilable, which means it must either include all its dependencies or use forward declarations.
-
Avoid long files. Consider splitting files longer than 3000 lines.
Modules
-
Structure your code using modules. Each module is a functional component that has a well specified role that may depend on other module.
-
Try to put one module per .c/.blk file and vice-versa.
-
Modules internal should remain private, and only a public, well maintained API should be exposed from the module.
-
For structure or union that need to be known by other modules, prefer exposing opaque types and accessors by default unless:
-
the type must be embedded in another structure for performance reasons
-
other modules need to perform frequent field accesses for which an accessor would have a noticeable performance impact
-
the type is explicitly designed to exchange data between modules
-
-
A module
bar
should be defined using thecore-module
framework.
static int bar_initialize(void *arg)
{
...
}
static int bar_shutdown(void)
{
...
}
MODULE_BEGIN(bar)
...
MODULE_END()
-
If
core-module
is not available on your environment, you must define the functions:
void bar_initialize(...);
void bar_shutdown(...);
-
void bar_initialize is problematic
, should return anint
: 0 for OK, non zero for errors
Code Documentation
-
do not use C++ style comments
//
. -
multi-line comments must begin with a star:
/* This is a very long comment which
* requires multiple lines to fit in
* this document.
*/
-
do not comment code using
/* */
constructs, always use the preprocessor:
#if 0 /* dead code */
dead_code(1, 2, 3);
#else
real_code(1, 2, 3);
#endif
/* dead_code(1, 2, 3); */ /* bad */
-
Use doxygen syntax to document functions that need it, and prefer to comment near the function prototype.
-
Use
FIXME
/TODO
(with this case) to flagFIXME
orTODO
. UseXXX
to flag a comment that must be read before trying to do anything with the code that follows. -
When documentation and code disagree, then both must be assumed wrong, and both must be rewritten.
Loops
-
Endless loops are always written using
for (;;)
.
while (true) { /* bad */
}
-
Always use the following syntax for reverse loops:
/* Good */
for (size_t i = limit; i-- > 0; ) {
}
/* Bad, condition is always true since i is unsigned */
for (size_t i = limit - 1; i >= 0; i--) {
}
Forbidden functions
-
Never, ever, try to use (non exhaustive, but quite):
strncpy
,strcpy
,strcat
,sprintf
,gets
,strtok
, … -
Do not use
malloc
,free
,realloc
,alloca
, orcalloc
directly: We havep_*
functions for that (p_new
/p_dup
/p_dupz
/p_delete
/…). Whenp_*
functions are not enough, then use themem_*
wrappers. -
Do not use
snprintf(buf, sizeof(buf), "%s", string)
, this is inefficient, usepstrcpy(buf, sizeof(buf), string);
-
We do
C99
, use theC99
bool
type, with lower casetrue
andfalse
.
Stack allocations
-
Never allocate an unchecked amount of memory on the stack (dynamic arrays or
alloca
), this can cause a stack overflow. -
Never uses
alloca()
in a loop. -
Use the
t_stack
when you need to perform dynamic-allocations on the stack. -
t_scope
should always be the first instruction of its scope.
{
t_scope;
char *buf = t_new_raw(char, BUFSIZ);
...
}
-
Functions allocating on
t_stack
for their caller should have a name prefixed byt_
.
static char *t_get_name(int id)
{
return t_fmt(NULL, "name-%d", id);
}
Parsers and protocols
Writing parsers
Parsing should be done using the pstream
(str-stream.h
) API (or
bit-stream.h
for bit level parsing). These API are designed to write robust
and maintainable parsers.
Implementing protocols
Implementing robust, consistent and maintainable protocols is even more
important than for parsers. Thus it is mandatory to use the
get_unaligned_cpu*
, get_unaligned_le*
, get_unaligned_be*
,
put_unaligned_cpu*
, put_unaligned_le*
, put_unaligned_be*
APIs from
arith-endianess.h
. (These APIs are also available within the pstream
APIs)
Even if we only support little endian, the le
APIs still need to be used to
emphasis the fact that we are parsing a little endian protocol. Same thing,
with hardware dependent protocols, the cpu
APIs must be used.
Debugging and error messages
Log messages format
Log messages should not begin with an uppercase letter and should not be terminated by a dot.
logger_error(&_G.logger, "this is a good error message");
logger_error(&_G.logger, "This is a bad error message.");
Assertions and expectations
No code must be put in assertions since code within assertions is not included in release builds.
Use expect()
to handle cases that should not happen but need to be properly
handled in release builds. expect()
are equivalent to assert()
in
development builds, and equivalent to the wrapped condition in release builds.
if (!expect(condition_that_should_be_true)) {
/* error case for production builds */
return -1;
}
/* normal case */
Abnormal conditions
Abnormal system conditions that we don’t want to deal with must terminate
processes using abort()
through logger_panic
. But other fatal issues like
bad configurations or initialization issues or any user related fatal error
shall not use logger_panic
but logger_fatal
instead. logger_fatal
and
logger_panic
are both catchable by a debugger in development mode.
logger_panic
cause a core dump, hence is quite frightening and should be kept
for cases where a core is required for post-mortem investigation.
/* good */
res = epoll_wait(epollfd, events, countof(events), -1));
if (res < 0) {
logger_panic(&_G.logger, "epoll_wait: %m");
}
/* bad */
cfg = parse_cfg("/some/path/some_product.conf");
if (!cfg) {
logger_panic(&_G.logger, "cannot read configuration file...");
}
logger_trace
levels
Debugging is performed using logger_trace
and logger_is_traced
macros (see
log.h
documentation for explanation on how to use both). Debuging levels
shall be used wisely.
-
level 0
:-
Those cannot be disabled for devel builds. It shall not be used. For consistency checks (invariants), use asserts or
logger_panic
.
-
-
level 1
:-
Messages for errors that we know how to fix, but are quite abnormal situations. For example: in a parser, when some value looks odd, and that we fix it with a side effect, we could use
logger_trace(&_G.logger, 1, "odd value %s in file %s", …)
. -
Tracing functions that are executed only once in a process life can be done using the level 1. For example, the module
_initialize
and_shutdown
functions are good candidates.
-
-
level 2
:-
Level 2 should absolutely not flood the output under normal conditions for a process run. If under some normal circumstances your
logger_trace
can generate copious outputs, do use a higher level. -
Messages that allow to understand important logic of a module, but that doesn’t flood the console too much. For example, one can trace every worker error in some machine this way.
-
-
level 3+
:-
Those are meant for advanced debugging of a module. Being verbose is less of an issue, as those levels are never enabled by default, and are meant to be enabled at a module level.
-
It is recommended to try that the tracing level 3 of a module remains decent in its output under debugging loads. For example, when using an injector with small rates of injection, level 3 should not trigger more than a few hits per second (under normal process behavior).
-
Really copious outputs should be used at level 4 (and more) only.
-