Choco OS  V.0.16.9.0
Join to the chocolate world
Coding Standard

Introduction

This is a document that describes coding style for the ChocoOS system sources. It is required to follow this rules in case of coding part of the system. This provides, that it will be easy to understand independently from author of the module. Moreover this rules helps to quickly find bugs and add new features to the existing code. Note, that each rule has assigned it's unique number with & sign. Thanks to that it is possible to use rule number during code review.

Rules

The first of all, the project is adapted to work with Eclipse IDE. There is special file, in the resources directory, named code_style.xml that can be imported in the eclipse environment. It contains general style for the system.

&1. Margin for the project is set to 140. This should be readable for most of todays screens.
&2. Do not use tabs - most of editors supports system, that replace tabs to spaces. The width of the tab should be set to 4 spaces. This is because tabs are treated by editors differently, but the code must look the same in each one.
&3. In each case, braces should be inserted in separated line. It is easier to find blocks using this rule.

For example this is bad:

if(){
// code
}

And this is good:

if()
{
// code
}

&4. Spaces should be inserted after opening parenthesis and before closing parenthesis like in the following example:

if( true == false )
{
// code
}

&5. Each block keyword (if,else,else if,do,while,for,switch) should be placed in new line (the 'do..while' is an exception from this rule)

Examples that shows this rule:

// This is correct
if( true )
{
foo();
}
else if( false )
{
do_sth();
}
else
{
bar();
}
// This is BAD!!
if( true )
{
foo();
} else
{
bar();
}
// 'do..while' exception:
do
{
foo();
} while( 0 );

&6. Single statement blocks are prohibited. The reason of it is that there is a risk, that someone will add new line to the block and will forgot that only the first line will executed. This is presented in the example below:

// This is BAD
if( condition ) foo();

Suppose, that someone want to add bar() function to this condition:

// This is still BAD
if( condition ) foo(); bar();

Note, that only foo() will be executed in the condition statement. The bar() will be executed in any time. The correct way to write this if is as follows:

// This is GOOD
if( condition )
{
foo();
bar();
}

&7. Avoid magic numbers - each number in the code should be defined as definition, value of enumerator, or constant variable in the place that provide usage of it everywhere when it can be useful. Every time, when you are need to place some number in the code, you should try to name it and create some definition for it. Only the 0 value does not need to be defined. The code below represents some examples for this rule:

// This is BAD
int findString( char ** Array )
{
int Index;
for(Index=9;Index<33;Index++)
{
if(strcmp(Array[Index],"hello")==0)
{
break;
}
}
return Index;
}

The example above shows magic numbers that are inserted into the function. It is not clear why these numbers were used - why the Index is initialized with 9, and not 0, why the Index is incremented to 33, and why it is compared with the "hello" string. Each one of these values should be defined. Here is correct version of this function:

// This is GOOD
#define ARRAY_STRING_START_INDEX 9
#define ARRAY_STRING_END_INDEX 33
#define SPECIAL_STRING "hello"
int findSpecialString( char ** Array )
{
int Index;
for(Index=ARRAY_STRING_START_INDEX;Index<ARRAY_STRING_END_INDEX;Index++)
{
if(strcmp(Array[Index],SPECIAL_STRING)==0)
{
break;
}
}
return Index;
}

&8. There can be only one return in function and only one break in block. When a function need to return some value, the variable should be created, and set to this value to return at the end of the function. The same is in case of 'break' keyword. Here some example:

bool Configure( void * Config )
{
bool result = false;
if( Config != NULL )
{
ReadConfig(Config);
result = true;
}
return result;

&9. Use enumerators instead of integers when it is possible. When you need to create some status type, commands list, etc, you should define a special enum type for it, instead of use universal integer. It is easier to find out what values can be stored in the type. For example:

// This is BAD
int DoSth( void * Config )
{
int Result = 0;
if( Config == NULL )
{
Result = EINVAL;
}
else if ( Config == Temp )
{
Result = NCORR;
}
return Result;
}
// This is GOOD
typedef enum
{
Result_InvalidArgument,
Result_CorrectValue ,
Result_Success
} Result_t;
Result_t DoSth( void * Config )
{
Result_t Result = Result_Success;
if( Config == NULL )
{
Result = Result_InvalidArgument;
}
else if ( Config == Temp )
{
Result = Result_CorrectValue;
}
return Result;
}

&10. Use the error code type (oC_ErrorCode_t from the oc_errors.h header) to return result of an operation everywhere where it is possible. You can add your own codes to the list, but you should firstly ensure, that this code not exist already.

&11. Each function, type, global variable, definition, macro and value, that is part of the system and is not local, must starts with prefix oC_. Note that when it is only local thing, it cannot contain it. This rule ensure, that names will not be duplicated, and helps to identify local names.

extern oC_ErrorCode_t oC_GPIO_Configure( oC_GPIO_Config_t * Config );
static bool SetMode( oC_GPIO_Mode_t Mode );

&12. Each function, type, global variable, definition, macro and value name, that is not local, must contain name of the module, that is a part of. Name of the module is given after oC_ prefix, as in example below:

// This is function from the module GPIO
oC_GPIO_DoSth( void );

There are some exceptions from this rule, but in each case it should be discussed with the ChocoOS team owner. Moreover it is possible to define a type, or function that is specific and unique for the module using only module name without type/function name. For example:

typedef void * oC_Mutex_t; // this is for Mutex module in oc_mutex.h

Moreover name of the module should be written with uppercase only when it is driver name, otherwise only the first letter of the word should be uppercase (also if a word is shortcut).

extern void oC_TIMER_TurnOn ( void ); // function of the driver named TIMER
extern oC_Mutex_t oC_Mutex_New ( void ); // function of the module Mutex
extern void oC_DevFs_FileOpen ( void ); // function of the DevFs module - Device File System

&13. Each type should be defined used typedef keyword, and the name must contain _t suffix. It is for comfortable usage of types, and for recognition of its. Here some example:

typedef uint8_t oC_Index_t;
int main( void )
{
oC_Index_t Index = 0;
return Index;
}

&14. Avoid using shortcuts. It is allowed only in special situations (in case of names longer than 60 characters or when the shortcut exist in standard C libraries), and in modules names.

&15. Do not use i,j,k for index variables. It is easier to find a bug and understand how it works, when the index is named as its destination. For example:

char usersNames[MAX_USERS][MAX_USER_NAME_LENGTH];
for(int userIndex;userIndex<MAX_USERS;userIndex++)
{
for(int characterIndex=0;characterIndex<MAX_USER_NAME_LENGTH;characterIndex++)
{
usersNames[userIndex][characterIndex] = '\0';
}
}

&16. Global variables must starts with uppercase for each word. Moreover, when the variable is not static, it must contain prefix with oC_ and name of a module. For example:

extern void * oC_GPIO_DefaultConfigurations[];
static void * CurrentConfigurations[MAX_CONFIGURATIONS];

&17. Local variables must starts with lowercase and then must contain uppercase for each word. Some examples below:

int main(void)
{
int firstVariable = 0;
int secondVariable = 0;
int result = 0;
return result;
}

&18. Name of variable must describe, what it stores. It cannot contain predicates, and there should be noun at the end of the name. Here examples:

// this is GOOD
int myVariable;
char personName[];
bool busyFlag;
// this is BAD
uint8_t someWeird; //there is no noun
int drink;

&19. Output parameters in functions must starts with out prefix as in example below:

static int ReadState( State_t * outState );

&20. Name of function must describe, what it do. It must be in order form, and it must contain predicate. There are only few exception from this rule - is and new functions types. Function names should be written without words: 'a', 'the' and 'an'. Here some examples:

// Some normal examples
static int AddUser( oC_User_t User );
static int RemoveUser( oC_User_t User );
static int RestartSystemAndWakeUpUser( oC_User_t User );
// 'is' function type exception:
static bool IsCorrect( oC_User_t User );
// 'new' function type exception:
static void * oC_Mutex_New( void );

&21. There is few special types of function, that should be defined always with following rules:

  • Set - Sets parameter to the value. It must return oC_ErrorCode_t Example:
    static oC_ErrorCode_t SetMode( Mode_t Mode );
  • Read - Reads parameter state and return it via function argument or reads data from a stream. It must get outData or outBuffer argument and must return oC_ErrorCode_t. Example:
    static oC_ErrorCode_t ReadMode( Mode_t* outMode);
  • Write - Writes data to a stream, for example to the SPI channel queue. It must return oC_ErrorCode_t, and get Data or Buffer argument. Example:
    static oC_ErrorCode_t WriteData( uint8_t Data );
  • Get - Returns parameter state via return value. It is important, that this function type is for reasons of speed operations, so it must not checks arguments. Example:
    static Mode_t GetMode( void );
  • Is - Checks if the argument is correct. It must return bool type. Example:
    static bool IsCorrect( Mode_t Mode );
  • TurnOn - Prepares a module to work. It must return oC_ErrorCode_t type. Example:
    oC_ErrorCode_t oC_GPIO_TurnOn( void );
  • TurnOff - Release module. It must work after call of this function. Function must return oC_ErrorCode_t type. Example:
    oC_ErrorCode_t oC_GPIO_TurnOff( void );
  • New - Allocates and initialize memory for an object. It must return a pointer to the allocated memory. Example:
    oC_User_t* oC_User_New( void );
  • Delete - Deletes and free memory allocated for an object. It must take a double pointer (pointer to pointer) to the object, set the object pointer to the NULL, and return oC_ErrorCode_t type. Example:
    oC_ErrorCode_t oC_User_Delete( oC_User_t** User);

&22. Enumerators values must starts with the name of the type (of course without _t suffix). Most of todays editors supports hints system. Thanks to that it will be easier to find values that are assigned for the enum type (for example in eclipse press Ctrl+Space). There is some example below, that illustrate this rule:

typedef enum
{
ExampleType_FirstValue ,
ExampleType_OtherValue ,
ExampleType_LastValue
} ExampleType_t;
// Now, we want to use this type:
ExampleType_t Value;
Value = ExampleType_ // when you write this string, and press Ctrl+Space, you will get hints from the eclipse with values, that you can
// assign to this type.

&23. Constant macros definitions must be named uppercase, with _ words seperated by _ sign. Modules names in it should also be written uppercase, but oC_ stay without changes. Example:

#define oC_GPIO_NUMBER_OF_PORTS 10

&24. Function-like macros should be named like functions

#define oC_Machine_IsChannelCorrect(MODULE_NAME,Channel)

&25. Hungarian notation is prohibited! Do not use any suffixes or prefixes that means what type is stored in the variable.

&26. The sign = in adjacent lines must always be aligned. The example below represent this rule:

int someVariable = 0;
int someOtherVariable = 0;

&27. Names of types and names of variables in variables definitions should also be aligned like in example:

SomeLongFooBarType_t fooBarVariable = 0;
int stringIndex = 0;
char * stringToReturn = NULL;

&28. Everytime when operation is repeated in adjacent lines without or with small changes, it should be aligned to emphasize these differences. Example:

CallFunction( Config, SomeVariable, Mode_Valid );
CallFunction( Config, SomeVariable, Mode_Invalid );
CallOtherFunction( Config, SomeOtherVariable );
CallOtherFunction( Config, SomeVariable );

&29. Each type in the system must be defined using typedef keyword. It is easier to use.

&30. Only name of the type is required. There is no reason to set also enum, or struct name. The example below shows this rule:

typedef enum _SomeEnumType_t
{
SomeEnumType_ValueZero ,
SomeEnumType_ValueOne
} SomeEnumType_t;

The _SomeEnumType_t word is not necessary.

&31. Fields in structures should be ordered via size from the biggest to the smallest one. This helps to save memory (alignment). Example:

typedef struct
{
uint32_t Buffer[100];
uint32_t BufferSize;
uint16_t Foo;
uint8_t Bar;
} ExampleStruct_t;

&32. Interface functions must check all arguments. It should be checked as much as possible. Note, that there are functions in the memory manager (in kernel for core layer) and in the standard memory libraries, that helps to verify if the pointer is correct. You should use them everywhere where it is a possible to give some pointer. Moreover it is possible to check the size of dynamic allocated address (from malloc type functions).

oC_ErrorCode_t oC_GPIO_Configure( const oC_GPIO_Config_t * Config )
{
oC_ErrorCode_t errorCode = oC_ErrorCode_ImlementError;
{
errorCode = oC_ErrorCode_None;
}
else
{
errorCode = oC_ErrorCode_WrongAddress;
}
return errorCode;
}

&33. Static and internal module functions must not check arguments. It should be done in interface functions.

&34. Use oC_ErrorCode_t type for as status. Dont be afraid to add new error codes, but always you could try to use existing.

&35. Error codes variables should in functions should be initialized as oC_ErrorCode_ImplementError. Thanks to that, when you will not set correct state for each condition in a function, you will receive an error, that you will find and track shortly. Remember, that always it is better to return failure, when everything is OK, than return success, when something is wrong. Example:

oC_ErrorCode_t oC_GPIO_Configure( const oC_GPIO_Config_t * Config )
{
oC_ErrorCode_t errorCode = oC_ErrorCode_ImplementError;
{
if( oC_GPIO_LLD_Configure( Config ) )
{
if(oC_GPIO_LLD_SetState(Config , 0x00) == false)
{
errorCode = oC_ErrorCode_GPIOLLDCantSetState;
}
}
else
{
errorCode = oC_ErrorCode_GPIOLLDConfigurationFailure;
}
}
else
{
errorCode = oC_ErrorCode_WrongAddress;
}
return errorCode;
}

&36. Definitions of variables should be ordered via size from the biggest to the smallest one. This helps to save memory (alignment). Example:

int main()
{
uint32_t Buffer[100];
uint32_t BufferSize;
uint16_t Foo;
uint8_t Bar;
// some code here
}

&37. Each file must contain oc_ prefix in the name. (standard libraries are exceptions from this rule)

&38. File name must be lowercase with _ instead of spaces. Example: oc_gpio.h

&39. File name must contain also module name, example: oc_gpio_defs.h

&40. Functions can take maximum 5 arguments. If some function need to more, it should be given using structures.

TODO:

  • file template description
  • sections definitions
  • protection again enabling enabled, and disable disabled modules
  • definition of variable at the start of the block.
  • functions cannot be longer than 60 lines
  • oC_AssignErrorCodes in if ()
  • one lines switches can be in one line with case and break
  • error code return variable should be named errorCode
  • &&, || operations at the start of the lines:
    if( Some == 1
    || SomeOther == 2
    )
  • function comments layout
  • files layout

Features:

  • dynamic memory overflow detector
  • allocators
  • memory events
  • heap maps
  • redefinition for unsigned integer types everywhere, where it can be changed in the future