Datalight Coding Style ====================== This is a description of the Datalight Coding Style intended for third parties who want to contribute code to Reliance Edge. This document is derived from the DDSS Coding Guidelines, but only contains a subset of the content which is most likely to be relevant to third party contributors. Reliance Edge complies with the MISRA-C:2012 coding guidelines, which includes many rules that affect coding style. Unfortunately the MISRA-C:2012 document is not freely available, and is much too long to be effectively summarized, but if you are familiar with the rules, adhere to them. A few important rules of thumb: avoid the goto and continue keywords; avoid using more than one break in a loop; and avoid having more than one return from a function (single point of exit); default cases in every switch statement; avoid recursion; and make generous use of parentheses. Outside of the file system driver, in tests and host tools, the MISRA-C rules are relaxed. Beyond MISRA-C, Datalight has a standard coding style. Most aspects of this style are matters of preference, but when contributing code to Datalight an effort should be made to use this style for the sake of consistency. Below is an example function, which illustrates several key points of Datalight Coding Style: /** @brief One-sentence description of what this function does. Additional description. @param ulFirstParameter Description of the parameter. @param pszPointer Description of the parameter. @return Describe the return value. @retval true Optional description of specific return value. @retval false Optional description of specific return value. */ bool ExampleFunction( uint32_t ulFirstParameter, char *pszPointer) { bool fStatus = true; /* This is a single-line comment. */ if(ulFirstParameter > 0U) { /* This is a multi-line comment. Filler text: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. */ FunctionCall(); while(fStatus) { fStatus = AnotherFunction(ulFirstParameter, pszPointer); } } return fStatus; } Tab Stop Conventions -------------------- In all C code (.c/.h), use a tab width of four spaces, and use soft tabs (in other words, tabs are expanded to spaces). In Makefiles, use hard tabs and a tab width of 8. Naming ------ Datalight uses CamelCase for functions and variables. Type names are generally UPPERCASE, except for standard types like uint32_t. Preprocessor macros are UPPERCASE, with words separated by underscores (for example, INODE_INVALID). Doxygen Documentation --------------------- Doxygen is used to document functions (including static functions), along with types, structures, files, etc. For Doxygen tags, use '@' instead of a backslash (thus "@param" not "\param"). Function Declarations --------------------- Multi-line function declarations are preferred, as they tend to be more readable. Use the following form: static bool ExampleFunctionDeclaration( uint32_t ulFirstParameter, char *pszPointer, uint8_t **ppbBuffer) { uint16_t uLocalVar; /* descriptive comment */ uint8_t *pbBuffer = NULL; /* descriptive comment */ Function body... } The following guidelines should be used: - Align both the data-type and the variable names, for parameters and locals, at the same level if practical. - For pointer types, the '*' belongs to the variable name---it's not part of the data-type, so keep it with the variable name. - If useful, single line comments may be used to describe local variables (not a requirement). - For functions with no parameters, the "void" declaration does not need to be on a separate line. - Generally each variable should be declared on a separate line. This promotes readability, and facilitates having a comment for each variable. Function declarations should be spaced apart by two blank lines between the closing brace which ends a function and the Doxygen comment which starts the next. Curly Braces ------------ Datalight lines up all curly braces vertically. As per MISRA-C, curly braces are never omitted, even if the braces contain only a single statement. For consistency, even structure declarations and initializations should use the same style, with the curly braces lined up vertically. One exception is for structure initializations where both the opening and closing curly braces can fit on the same line. If so, do it. Code Comments ------------- Datalight uses the standard C style /* comments */. C++ style comments (//) are never used. The Datalight standard comment style is shown below. This style applies to all general comments within the code. /* This is a single-line comment. */ if(ulFirstParameter > 0U) { /* This is a multi-line comment. Filler text: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. */ while(fStatus) { } } Note the characteristics: - The /* and */ align with the natural 4 character indentation. - The comment text is exactly indented another 4 characters. - The comment text starts on the same line as the opening /*. - The terminating */ is on its own line. - There is usually a single blank line preceding the comment, however if the preceding line is an opening curly brace, then an extra blank line is not necessary. - There is usually no blank line after the comment, but rather the closing */ "attaches" the comment to the code about which the comment refers. - These comments should always fit with the standard 80 character margin. Comments where the /* and */ are on the same line may be used in a few places: - For variable or parameter descriptions, where the comment fits on the same line as the declaration. - For structure member declarations, where the comment fits on the same line as the declaration. - For macros or preprocessor logic, where the comment fits on the same line. It is OK for such comments to exceed the 80 character margin by a small amount, if necessary, as this sometimes promotes code readability. Indentation Style ----------------- The general paradigm used in Datalight code is that curly braces line up vertically, and everything in between them is indented. This should include all comments, labels, and preprocessor symbols. The only things which are aligned at the left-most columns are: - Symbols, variables, declarations, and preprocessor logic which are at the module-scope (outside of a function) - Comments which are outside of a function - Function declarations - Function open and closing curly braces Typically comments are always lined up directly with the code to which they apply. Labels (when used; gotos are disallowed in driver code) are lined up two characters to the left of the code they reside in, to make them stand out, while as the same time, still remaining subservient to the level of curly braces in which they reside. For example: bool ExampleLabelUsage(void) { MutexLock(); Lots of complicated code... Unlock: MutexUnlock(); return fSuccess; } Preprocessor logic, such as controlling features which are conditionally compiled in or out, should not disrupt the flow of the code, but rather should be indented in similar fashion to the code it controls, but positioned two characters to the left. For example, consider the following code snippet. The preprocessor conditions are both indented relative to the outer curly braces, but do not disrupt the normal code flow. int32_t red_statvfs( const char *pszVolume, REDSTATFS *pStatvfs) { REDSTATUS ret; ret = PosixEnter(); if(ret == 0) { uint8_t bVolNum; ret = RedPathSplit(pszVolume, &bVolNum, NULL); #if REDCONF_VOLUME_COUNT > 1U if(ret == 0) { ret = RedCoreVolSetCurrent(bVolNum); } #endif if(ret == 0) { ret = RedCoreVolStat(pStatvfs); } PosixLeave(); } return PosixReturn(ret); } Note that, like anything else between curly brackets, the contents of a switch statement are indented: switch(ulSignature) { case META_SIG_MASTER: fValid = (uFlags == BFLAG_META_MASTER); break; case META_SIG_IMAP: fValid = (uFlags == BFLAG_META_IMAP); break; case META_SIG_INODE: fValid = (uFlags == BFLAG_META_INODE); break; case META_SIG_DINDIR: fValid = (uFlags == BFLAG_META_DINDIR); break; case META_SIG_INDIR: fValid = (uFlags == BFLAG_META_INDIR); break; default: fValid = false; break; } Maximum Line Length ------------------- The maximum line length for code need not be rigidly limited to the traditional 80 characters. Nevertheless the line lengths should be kept reasonable. Anything longer than 100 to 120 characters should probably be broken up. The most important consideration is readability---fitting on the screen is important for readability, but equally important is facilitating an easy understanding of the logical code flow. There are a few exceptions on both sides of the issue. Generally comments should be limited to 80 characters always. Some lines of code may exceed the 120 character length by a large margin, if it makes the code more understandable and maintainable. This is especially true when dealing with code that generates output which needs to be lined up. Regardless of everything else, no lines should exceed 250 characters because some editors cannot handle anything larger. Maximum Display Output Line Length ---------------------------------- Any code which displays TTY style output, whether on a screen or a terminal, should be constructed so the output is readable and wraps properly on an 80 character wide display. This primarily applies to the "standard" output from various tests and tools as well as syntax output for those tests and tools; debug output can violate this rule. Preprocessor Notation --------------------- Don't use preprocessor notation where the # is separated from the keyword by one or more white spaces. For example, don't do: #ifndef SYMBOL1 # define SYMBOL1 #endif Instead, do: #ifndef SYMBOL1 #define SYMBOL1 #endif Hexadecimal Notation -------------------- Use uppercase for any alphabetic hexadecimal digits, and lower case for the notational element. For example: #define HEXNUM 0x123abd /* Bad */ #define HEXNUM 0X123ABD /* Bad */ #define HEXNUM 0x123ABD /* Good */ Hungarian Notation ------------------ Datalight uses Hungarian notation. The following type prefixes are used: Type Prefix | Meaning ----------- | ------- c | char uc | unsigned char i | int n | unsigned int or size_t b | uint8_t u | uint16_t ul | uint32_t ull | uint64_t sz | array of char that will be null-terminated f | bool h | A handle fn | A function (always used with the "p" modifier) There is no official Hungarian for int8_t, int16_t, int32_t, or int64_t, although some code uses unofficial variants (like "ll" for int64_t). The following modifiers may be used in combination with the type prefixes defined above, or in combination with other types: Modifier | Meaning -------- | ------- a | An array p | A pointer g | A global variable Notes: - There is no standard Hungarian for structure declarations, however the use of the "a" and "p" modifiers is completely appropriate (and expected). - For those data types which do not have any standard defined Hungarian prefix, using none is preferable to misusing another prefix which would lead to confusion. - The "p" pointer modifier must be used such that a variable which is a pointer to a pointer uses multiple "p" prefixes. A general rule-of-thumb is that the variable name should have the same number of "p" prefixes as the declaration has asterisks. This allows pointer expressions to be easily decoded using cancellation. Variable Scope -------------- Declare a variable in the narrowest scope in which it is meaningful. Unnecessarily declaring all variables at the beginning of a function, where they may be physically far from where they are actually used, makes the code harder to maintain. When multiple blocks of code share a variable, but not its value, declare the variable separately for each code block. For example, if two separate blocks contain loops indexed by a variable ulIndex declare it separately in each block rather than declaring it once in a wider scope and using it in both places. Using distinct declarations in the two blocks allows the compiler to check for failure to initialize the variable in the second block. If there is a single declaration, the (now meaningless) value left over from the first block can be used erroneously in the second block.