The purpose of having coding standards is to provide a guiding format for code development. This facilitates the ease of construction when multiple programmers are developing separate or even shared projects. When mixing styles or standards, maintaining that code and in different coding philosophies becomes difficult. The ultimate goal is to elevate some of the complexity and confusion that may arise in project development and to ease entry into existing projects by unfamiliar developers.
“Clarity affords focus.” Thomas Leonard
Folder Structure
File numbers can grow into excessive numbers. Some basic folder structure can alleviate this with the following folder construction.
doc : All files related to instruction or documentation of project. To include manufactures application notes and related user manuals.
datasheet : Projects consisting of peripherals or hardware interfacing will need to follow the manufacturers datasheet. For this purpose,
datasheets for the hardware should be placed here.
examples: The examples found here will be code examples that use the library and can include multiple platforms. Platforms need to to be separated into folders and labeled to their respective platform
library: Driver files, both source and headers are included here. They are separated into respective source and header types.
package: Compressed libraries are stored here for use with MikroE. Package manager.
File Naming Convention
File names are made up of a base name, and an optional period and suffix. The first character of the name should be a letter and all characters (except the period) should be lower-case letters and numbers. The base name should be eight or fewer characters and the suffix should be three or fewer characters (four, if you include the period). These rules apply to both program files and default files used and produced by the program.
The following suffices are required:
- c source file names must end in .c
- c header file names must end in .h
- Assembler source file names must end in .asm
In addition, it is conventional to use a “README.md” file for summary of the contents of the directory and/or directory tree. It is recommended to document both "the bigger picture" and issues for the program as a whole in the README
Source Files
Suggested sections of a C source file are the following:
- Describe what is in the file. A description of the modules or external functions that list how they are to be used. Style used is in the Doxygen standard formatting. Example:
/** * @file magneto.c * @brief This module contains the implementation of the magneto hardware * functions.
- Author(s) name(s) and date of when the file was created and licensing information. Revision information and external references.
* @author Richard Lowe * * @date 23/04/2015 * * @version 0.1a * * @copyright GNU Public License * *
- External Link
- Test configuration needs to be specific to the type of configuration you are using.
* Test configuration: * * MCU: STM32F107VC * Dev.Board: EasyMx PRO v7 for STM32 * Oscillator: HS-PLL 72.0000 MHz, internal 8.0000 MHz RC * Ext. Modules: BEE Click Board * SW: MikroC PRO for ARM v4.0
- External file inclusions need to follow the end of the main comment block. Using
for system inclusions and “file.h” for user defined. /************************************************************************** * Includes **************************************************************************/ #include "magneto.h" #include "magneto_hal.h" #include
- Any defines and typedefs that apply to the file as a whole are next. Normal order is to have “constant” macros first, than “function” macros, then typedefs and enums.
/************************************************************************** * Module Preprocessor Constants **************************************************************************/ #define MAX_DEVICES 10 #define MIN_DEVICES 1 /************************************************************************** * Module Preprocessor Macros **************************************************************************/ #define MIN(a, b) (((a) < (b)) ? (a) : (b)) /************************************************************************** * Module Typedefs **************************************************************************/
- Next come the global (external) data declarations, usually in the order: externs, non-static globals, static globals. If a set of defines applies to a particular piece of global data (such as a flags word), the defines should be immediately after the data declaration or embedded in structure declarations, indented to put the defines one level deeper than the first keyword of the declaration to which they apply.
/************************************************************************** * Module Variable Definitions **************************************************************************/ static uint8_t device_count; static uint8_t device_addresses[MAX_DEVICES]; static int16_t zero[MAX_DEVICES]; static bool error_flag; static magneto_mode_t mode_current;
- The functions come last, and should be in some sort of meaningful order. Like functions should appear together. A "breadth-first" approach (functions on a similar level of abstraction together) is preferred over depth-first (functions defined as soon as possible before or after their calls). In order of call is a good approach. Considerable judgment is called for here. If defining large numbers of essentially-independent utility functions, consider alphabetical order.
Header Files
Header files are files that are included in other files prior to compilation by the C pre-processor. Some, such as stdint.h, are defined at the system level and must included by any program using the standard integer definitions. Header files are also contain data declarations and defines that are needed by more than one program. Header files should be functionally organized, i.e., declarations for separate subsystems should be in separate header files. Also, if a set of declarations is likely to change when code is ported from one machine to another, those declarations should be in a separate header file.
Avoid private header file names that are the same as library header file names. The statement #include "math.h" will include the standard library math header file if the intended one is not found in the current directory. If this is what you want to happen, comment this fact. Do not use absolute path names for header files. Use the
Header files that declare functions or external variables should be included in the file that defines the function or variable. That way, the compiler can do type checking and the external declaration will always agree with the definition.
Defining variables in a header file is often a poor idea. Frequently it is a symptom of poor partitioning of code between files. Also, some objects like typedefs and initialized data definitions cannot be seen twice by the compiler in one compilation. On some systems, repeating uninitialized declarations without the extern keyword also causes problems. Repeated declarations can happen if include files are nested and will cause the compilation to fail.
Header files should not be nested. The prologue for a header file should, therefore, describe what other headers need to be #included for the header to be functional. In extreme cases, where a large number of header files are to be included in several different source files, it is acceptable to put all common #includes in one include file.
It is common to put the following into each .h file to prevent accidental double-inclusion.
#ifndef EXAMPLE_H #define EXAMPLE_H ... /* body of example.h file */ #endif /* EXAMPLE_H */
This double-inclusion mechanism should not be relied upon, particularly to perform nested includes.
Comments
The comments should describe what is happening, how it is being done, what parameters mean, which globals are used and which are modified, and any restrictions or bugs. Short comments should be what comments, such as "compute mean value", rather than how comments such as "sum of values divided by n". C is not assembler; putting a comment at the top of a 3-10 line section telling what it does overall is often more useful than a comment on each line describing micro-logic.
Comments that describe data structures, algorithms, etc., should be in block comment form with the opening /* in columns 1-2, a * in column 2 before each line of comment text, and the closing */ in columns 2-3.
/* * Here is a block comment. * The comment text should be tabbed or spaced over uniformly. * The opening slash-star and closing star-slash are alone on a line. */
Very long block comments such as drawn-out discussions and copyright notices often start with /* in columns 1-2, no leading * before lines of text, and the closing */ in columns 1-2. Block comments inside a function are appropriate, and they should be tabbed over to the same tab setting as the code that they describe. One-line comments alone on a line should be indented to the tab setting of the code that follows.
if(UART_Data_Ready()) { /* Get char from buffer */ char read_buffer = UART_Read(); }
Very short comments may appear on the same line as the code they describe, and should be tabbed over to separate them from the statements. If more than one short comment appears in a block of code they should all be tabbed to the same tab setting.
if(a == EXCEPTION) { b = TRUE; /* special case */ } else { b = is_prime(a); /* works only for odd a */ }
Commenting Keywords
These are keywords that will be parsed out of the comments to produce documents and reports. The ease of automated documentation tools have made it a staple of the modern developer. These are some of the keywords that are encouraged to use.
@author specifies the author of the module
@version specifies the version of the module
@param specifies a parameter into a function
@return specifies what a function returns
@todo what remains to be done
@bug report a bug found in the piece of code
Declarations
Global declarations should begin in column 1. All external data declaration should be preceded by the extern keyword. If an external variable is an array that is defined with an explicit size, then the array bounds must be repeated in the extern declaration unless the size is always encoded in the array (e.g., a read-only character array that is always null-terminated). Repeated size declarations are particularly beneficial to someone picking up code written by another.
The "pointer" qualifier, '*', should be with the variable name rather than with the type.
char *s,*t,*u;
instead of
char* s,t,u;
which is wrong, since 't' and 'u' do not get declared as pointers.
Unrelated declarations, even of the same type, should be on separate lines. A comment describing the role of the object being declared should be included, with the exception that a list of #defined constants do not need comments if the constant names are sufficient documentation. The names, values, and comments are usually tabbed so that they line up underneath each other. Use the tab character rather than blanks (spaces). For structure and union template declarations, each element should be alone on a line with a comment describing it. The opening brace ({) should be on the same line as the structure tag, and the closing brace (}) should be in column 1.
struct weather { int8_t wind_speed; /**< wind speed in whole number */ uint16_t earth_movement; /**< earth quake warning */ float temperature; /**< outside temperature */ }; /* defines for weather types */ #define WIND(1) #define RAIN(2) #define SNOW(3) #define HAIL(4) #define SLEET(5)
These defines are sometimes put right after the declaration of type, within the struct declaration, with enough tabs after the '#' to indent define one level more than the structure member declarations. When the actual values are unimportant, the enum facility is better
enum weather_type { WIND=1, RAIN, SNOW, HAIL, SLEET }; struct weather_values { int8_t wind_speed; /**< wind speed in whole number */ uint16_t earth_movement; /**< earth quake warning */ float temperature; /**< outside temperature */ };
Any variable whose initial value is important should be explicitly initialized, or at the very least should be commented to indicate that C's default initialization to zero is being relied upon. The empty initializer, "{}", should never be used. Structure initialization should be fully parenthesized with braces. Constants used to initialize longs should be explicitly long. Use capital letters; for example two long "2l" looks a lot like "21", the number twenty-one.
int count = 1; char *msg = "hello world"; struct weather_values weather[] = { { 22, RAIN, 21.3f }, { 18, RAIN, 22.4f }, { 4, SNOW, 1.2f }, };
In any file which is part of a larger whole rather than a self-contained program, maximum use should be made of the static keyword to make functions and variables local to single files. Variables in particular should be accessible from other files only when there is a clear need that cannot be filled in another way. Such usage should be commented to make it clear that another file's variables are being used; the comment should name the other file.
The most important types should be highlighted by typedef-ing them, even if they are only integers, as the unique name makes the program easier to read. Structures may be typedef-ed when they are declared. Give the struct and the typedef the same name.
typedef struct student_t { int16_t student_id; char *student_name; char *student_alias; } student_t;
The return type of functions should always be declared. If function prototypes are available, use them. One common mistake is to omit the declaration of external math functions that return double. The compiler then assumes that the return value is an integer and the bits are dutifully converted into a (meaningless) floating point value.
Brace Placement
Of the three major brace placement strategies one is recommended:
if(condition) { ... } while(condition) { ... }
A Line Should Not Exceed 78 Characters
Even though with big monitors we stretch windows wide our printers can only print so wide. And we still need to print code. Nobody likes to scroll sideways when reading code.
If Then Else Formatting
Different bracing styles will yield slightly different looks. Java and JavaScript can easily be recognized by the bracing placed directly after the condition. In C the common format is also recognizable by the following format:
if(condition) { ... } else if(condition) { ... } else { ... }
Switch Formatting
Falling through a case statement into the next case statement shall be permitted as long as a comment is included. The default case should always be present and trigger an error if it should not be reached, yet is reached. If you need to create variables put all the code in a block.
switch(...) { case 1: ... /* comments */ break; case 2: { int v; ... } break; default: }
Goto
Goto statements should be used sparingly, as in any well-structured code. The main place where they can be usefully employed is to break out of several levels of switch, for, and while nesting, although the need to do such a thing may indicate that the inner constructs should be broken out into a separate function, with a success/failure return code.
for(...) { while(...) { ... if (disaster) { goto error; } } } ... error: clean_mess();
When a goto is necessary the accompanying label should be alone on a line and to the left of the code that follows. The goto should be commented (possibly in the block header) as to its utility and purpose.
Continue and Break
Continue and break are really disguised gotos so they are covered here. Continue and break like goto should be used sparingly as they are magic in code.
The two main problems with continue are:
- It may bypass the test condition
- It may bypass the increment/decrement expression
Consider the following example where both problems occur:
while(condition) { ... if(/* some condition */) { continue; } ... if ( i++ > STOP_VALUE) break; }
Mixing continue with break in the same loop is a sure way to disaster.
Ternary
The trouble is people usually try and stuff too much code in between the ? and :. Here are a couple of clarity rules to follow:
- Put the condition in parens so as to set it off from other code
- If possible, the actions for the test should be simple functions.
- Put the action for the then and else statement on a separate line unless it can be clearly put on one line.
Example
(condition) ? true : false;
or
(condition) ? long statement : another long statement;
One Statement Per Line
There should be only one statement per line unless the statements are very closely related.
The code is easier to read. Use some white space too. Nothing better than to read code that is one line after another with no white space or comments.
One Variable Per Line
NO:
char **a, *x;
YES:
char **a = 0; /* add doc */ char *x = 0; /* add doc */
Some good points to this style is:
- Documentation can be added for the variable on the line.
- It's clear that the variables are initialized.
- Declarations are clear which reduces the probability of declaring a pointer when you meant to declare just a char.