From 0d051ef3f081f05606bae9d798a6aac630db53f0 Mon Sep 17 00:00:00 2001 From: L-diy Date: Mon, 23 Oct 2023 21:09:32 +0200 Subject: [PATCH] Add a style guide --- style_guide.md | 531 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 style_guide.md diff --git a/style_guide.md b/style_guide.md new file mode 100644 index 0000000..12bf495 --- /dev/null +++ b/style_guide.md @@ -0,0 +1,531 @@ +# C Style Guide and Rules + +## Table of contents +- [General rules](#general-rules) +- [Comments](#comments) +- [Functions](#functions) +- [Structures, enumerations, typedefs](#structures-enumerations-typedefs) +- [Compound statements](#compound-statements) +- [Macros and preprocessor directives](#macros-and-preprocessor-directives) +- [Documentation](#documentation) +- [Header/source files](#headersource-files) + +## Conventions used +MUST, MUST NOT, SHOULD, SHOULD NOT, MAY and OPTIONAL are used as described in [RFC 2119](https://tools.ietf.org/html/rfc2119). + +## General rules + +- Do not use tabs, use spaces instead +- Use `4` spaces per indent level +- Use `1` space between keyword and opening bracket +```c +// OK +if (condition) +while (condition) +for (init; condition; step) +do {} while (condition) + +// Wrong +if(condition) +while(condition) +for(init;condition;step) +do {} while(condition) +``` + +- Limit the line length to `120` characters, use `120` characters as a soft limit. +- Do not use space between function name and opening bracket +```c +int32_t a = sum(4, 3); // OK +int32_t a = sum (4, 3); // Wrong +``` + +- Use only lowercase characters for variables/functions/types with optional underscore `_` char +- Opening curly bracket is always at the same line as keyword (`for`, `while`, `do`, `switch`, `if`, ...) +```c +size_t i; +for (i = 0; i < 5; ++i) { // OK +} +for (i = 0; i < 5; ++i){ // Wrong +} +for (i = 0; i < 5; ++i) // Wrong +{ +} +``` + +- Use single space before and after comparison and assignment operators +```c +int32_t a; +a = 3 + 4; // OK +for (a = 0; a < 5; ++a) // OK +a=3+4; // Wrong +a = 3+4; // Wrong +for (a=0;a<5;++a) // Wrong +``` + +- Use single space after every comma +```c +func_name(5, 4); // OK +func_name(4,3); // Wrong +``` + +- Do not initialize `global` variables to any default value (or `NULL`), implement it in the dedicated `init` function (if REQUIRED). + +```c +static int32_t a; // OK +static int32_t b = 4; // Wrong + +void my_module_init(void) { + a = 0; + b = 4; +} +``` + +- Declare local variables in order + 1. Custom types, structures and enumerations + 2. Integer types + 3. Single/Double floating point +```c +int my_func(void) { + // 1 + my_struct_t my; + my_struct_ptr_t* p; + + // 2 + uint32_t a; + char h; + + // 3 + double d; + float f; +} +``` + +- Always declare local variables at the beginning of the block, before the first executable statement +- Always add trailing comma in the last element of structure (or its children) initialization. Unless the structure is very simple and short +```c +typedef struct { + int a, b; +} str_t; + +str_t s = { + .a = 1, + .b = 2, // Comma here +} + +// No trailing commas - only for small and simple structures +static const str_t y = {.a = 1, .b = 2}; +``` + +- Declare counter variables in `for` loop +```c +// OK +for (size_t i = 0; i < 10; ++i) + +// OK, if you need the counter variable later +size_t i; +for (i = 0; i < 10; ++i) { + if (...) { + break; + } +} +if (i == 10) { + +} + +// Wrong +size_t i; +for (i = 0; i < 10; ++i) ... +``` + +- Except `char`, `float` or `double`, always use types declared in `stdint.h` library, eg. `uint8_t` for `unsigned 8-bit`, etc. +- Never compare against `true`, eg. `if (check_func() == 1)`, use `if (check_func()) { ... }` +- Always compare pointers against `NULL` value +```c +void* ptr; + +// OK, compare against NULL +if (ptr == NULL || ptr != NULL) { + +} + +// Wrong +if (ptr || !ptr) { + +} +``` +- Always use `size_t` for length or size variables +- Always use `const` for a pointer if function should not modify memory pointed to by `pointer` +- Always use `const` for a function parameter or variable, if it should not be modified +```c + +/** + * When d could be modified, data pointed to by d could not be modified + */ +void my_func(const void* d) { + +} + +/** + * When d and data pointed to by d both could not be modified + */ +void my_func(const void* const d) { + +} + +/** + * When d should not be modified inside function, only data pointed to by d could be modified + */ +void my_func(void* const d) { + +} +``` + +- When a function may accept a pointer of any type, always use `void *`, do not use `uint8_t *` + - The function MUST take care of proper casting in implementation +- Always use brackets with `sizeof` operator +- Always compare variable against zero, except if it is treated as `boolean` type +- Never compare `boolean-treated` variables against zero or one. Use NOT (`!`) instead +```c +size_t length = 5; // Counter variable +uint8_t is_ok = 0; // Boolean-treated variable +if (length) // Wrong, length is not treated as boolean +if (is_ok) // OK, variable is treated as boolean +if (!is_ok) // OK +if (is_ok == 1) // Wrong, never compare boolean variable against 1! +if (is_ok == 0) // Wrong, use ! for negative check +``` + +- Every function MUST include *doxygen-enabled* comment, even if function is `static` +- Use English names/text for functions, variables, comments +- Use *lowercase* characters for variables +- Use *underscore* if variable contains multiple names, eg. `force_redraw`. Do not use `forceRedraw` +- Always use `<` and `>` for C Standard Library include files, eg. `#include ` +- Always use `""` for custom libraries, eg. `#include "my_library.h"` +- When casting to a pointer type, always align the asterisk to the type, eg. `uint8_t* t = (uint8_t*)var_width_diff_type` + +## Comments + +- Start every comment with capital letter, if possible +- Use `//` for single-line comments, start comment with single space +```c +// This is comment (ok) +/* This is comment (wrong) */ +``` + +- For multi-line comments use `space+asterisk` for every line +```c +/* + * This is multi-line comments, + * written in 2 lines (ok) + */ + +/** + * Wrong, use double-asterisk only for doxygen documentation + */ + +/* +* Single line comment without space before asterisk (wrong) +*/ + +/* + * Single line comment in multi-line configuration (wrong) + */ +``` + +- Make comments aligned to 4-spaces indent from the beginning of line, and aligned with the rest of the block +```c +void my_func(void) { + char a, b; + + a = call_func_returning_char_a(a); // This is a comment + b = call_func_returning_char_b_longer_func(a); // This is a comment, aligned to 4-spaces indent +} +``` + +## Functions + +- Every function which may have access from outside its module, must include function *prototype* (or *declaration*) in the header file +- Every function that is used only inside its module: + - Must be declared as `static` + - Must NOT include function prototype in the header file + - Must include function prototype in the source file +- Function name MUST be lowercase, optionally separated with underscore `_` character +```c +// OK +void my_func(void); +void myfunc(void); + +// Wrong +void MYFunc(void); +void myFunc(); +``` + +- When function returns pointer, align the asterisk to the return type +```c +// OK +const char* my_func(void); +my_struct_t* my_func(int32_t a, int32_t b); + +// Wrong +const char *my_func(void); +my_struct_t * my_func(void); +``` + +## Structures, enumerations, typedefs + +- Structure or enumeration name MUST be lowercase with optional underscore `_` character between words +- Structure or enumeration may contain `typedef` keyword +- All structure members SHOULD be lowercase +- All enumeration members MUST be uppercase + +When structure is declared, it may use one of `3` different options: + +1. When structure is declared with *name only*, it *MUST not* contain `_t` suffix after its name. +```c +struct struct_name { + char* a; + char b; +}; +``` +2. When structure is declared with *typedef only*, it *has to* contain `_t` suffix after its name. +```c +typedef struct { + char* a; + char b; +} struct_name_t; +``` +3. When structure is declared with *name and typedef*, it *MUST NOT* contain `_t` for basic name and it *MUST* contain `_t` suffix after its name for typedef part. +```c +typedef struct struct_name { + char* a; + char b; + char c; +} struct_name_t; +``` + +- When new typedef is introduced for function handles, use `_fn` suffix +```c +typedef uint8_t (*my_func_typedef_fn)(uint8_t p1, const char* p2); +``` + +## Compound statements + +- Every compound statement MUST include opening and closing curly bracket, even if it includes only `1` nested statement +- Every compound statement MUST include single indent; when nesting statements, include `1` indent size for each nest +```c +// OK +if (c) { + do_a(); +} else { + do_b(); +} + +// Wrong +if (c) + do_a(); +else + do_b(); + +// Wrong +if (c) do_a(); +else do_b(); +``` + +- In case of `if` or `if-else-if` statement, `else` MUST be in the same line as closing bracket of the first statement +```c +// OK +if (a) { + +} else if (b) { + +} else { + +} + +// Wrong +if (a) { + +} +else { + +} + +// Wrong +if (a) { + +} +else +{ + +} +``` + +- In case of `do-while` statement, `while` part MUST be in the same line as closing bracket of `do` part +```c +// OK +do { + /* ... */ +} while (check()); + +// Wrong +do +{ + /* ... */ +} while (check()); + +// Wrong +do { + /* ... */ +} +while (check()); +``` + +- Compound statement MUST include curly brackets, even in the case of a single statement. +- Avoid incrementing variables inside loop block if possible. + +### Switch statement +- Always include `default` statement +- Add *single indent* for every `case` statement +```c +// Ok +switch (check()) { + case 0: + do_a(); + break; + case 1: + do_b(); + break; + default: + break; +} + +// Wrong +switch (check()) { +case 0: // Wrong, case indent missing + do_a(); + break; // Wrong, break MUST have indent as it is under case + case 1: + do_b(); // Wrong, indent under case is missing + break; + default: + break; +} +``` + +## Macros and preprocessor directives + +- Always use macros instead of literal constants, especially for numbers +- Always protect input parameters with parentheses +- All macros MUST be fully uppercase, with optional underscore `_` character between words +```c +// OK +#define SQUARE(x) ((x) * (x)) + +// Wrong +#define square(x) ((x) * (x)) + +// Wrong +#define MIN(x, y) x < y ? x : y +``` + +- - Always document `if/elif/else/endif/ifdef/ifndef` statements +```c +#ifdef XYZ +/* do something */ +#endif /* XYZ */ +``` + +- Do not indent sub statements inside `#if` statement + +## Documentation + +- Use doxygen-enabled documentation style for `variables`, `functions` and `structures/enumerations` +- Always use `@` for doxygen, do not use `\` + +```c +/** + * @brief The main screen + */ +lv_obj_t * main_screen; +``` + +- Every structure/enumeration member MUST include documentation +```c +/** + * @brief Struct containing information about a wifi network + * @note The ssid is null terminated + */ +typedef struct { + char ssid[33]; + int8_t rssi; + ui_wifi_auth_mode_t auth_mode; +} ui_wifi_network_t; +``` + +- Documentation for functions used outside its module MUST be placed above function prototype in header file, that is, documentation that describes WHAT the function does. +- Documentation that describes HOW the function does it be placed with the function implementation. +- For functions that have only internal use, documentation MUST be placed with the function implementation. +- All functions MUST include a `brief` description +- Every parameter MUST be noted if it is `in` or `out` for *input* and *output* respectively +- Function MUST include `return` parameter if it returns something. This does not apply for `void` functions +- Function can include other doxygen keywords, such as `note` or `warning` +- If function returns member of enumeration, use `ref` keyword to specify which one +```c +/** + * @brief Parse and publish data from the meter data history endpoint + * + * @param[in] buf The buffer containing the JSON data + * @param[in] len The length of the JSON data + * @return @ref ESP_OK on success, member of @ref esp_err_t otherwise + */ +static esp_err_t parse_publish_meter_data_history(uint8_t *buf, uint32_t len) { + /* ... */ +} +``` + +## Header/source files + +- Leave a single empty line at the end of the file +- Every file MUST include doxygen annotation for `file` and `brief` description followed by empty line +```c +/** + * @file tsc2046.c + * @brief TSC2046 touch screen controller driver + */ + // Empty line +``` + +- Header file MUST include guard `#ifndef` +- Include external header files with STL C files first followed by application custom files +- Header file MUST include only every other header file to compile correctly, but not more (.c should include the rest if REQUIRED) +- Header file MUST only expose module public variables/types/functions +- Use `extern` for global module variables in header file, define them in source file later +``` +/* file.h ... */ +#ifndef ... + +extern int32_t my_variable; // This is global variable declaration in header + +#endif + +/* file.c ... */ +int32_t my_variable; // Actually defined in source +``` + +- Never include `.c` files in another `.c` file +- Do not include module private declarations in header file + +- Header file example +```c +/* License comes here */ +#ifndef TEMPLATE_HDR_H +#define TEMPLATE_HDR_H + +/* Include headers */ +#include +#include "my_custom_header.h" + +/* File content here */ + +#endif /* TEMPLATE_HDR_H */ +``` \ No newline at end of file