10 main rules for killing beetles

  • Tutorial
I understand that the title looks like a machine translation, but I could not come up with the best equivalent of “Top 10 Bug-Killing Coding Standard Rules”.

10 main rules for avoiding bugs - suggested in the comments

This post is a free retelling of key concepts of Michael Barr’s book “Embedded C Coding Standard”, set forth in his speech at a webinar in June this year (I don’t know how to put the tag “translation”).
Some rules apply only to C ++ and C extensions, and some apply to the language standard.

Rule 1

Curly braces {} should ALWAYS frame a code block after if, else, switch, while, do, for statements.
Corollary: A single operator and an empty expression must also be surrounded by curly braces.

Proof: consider a simple code example
if (5 == foo)
   bar();
always_run();


It seems to be okay, everything is clear and clear, but we decided to temporarily disable the call to the bar function (for example, when searching for the place where the program crashes).

if (5 == foo)
//   bar();
always_run();


and quite unexpectedly, the run function almost ceased to be called, and if the brackets were in place, this did not happen.

if (5 == foo) {
//   bar();
};
always_run();


The rule seems to be obvious and repeatedly found in various sources, but how hard it is to follow it, because the screen space is taken up under an empty line with a closing bracket, and this place is so pathetic. (Note that I, in violation of another rule, which is not included in the top 10, nevertheless added conditions to the operator and was able to save a whole line). A small remark - I understand perfectly well that a line with a single closing bracket does not cost anything in the sense of compilation, but, as you know, we write programs not for computers, but for other people, and, for example, the code is much clearer to me when I can see everything on the screen without the need for rolling.

Rule 2

Use the const keyword wherever possible.
Consequences: Use when describing variables that will not change after initialization; when describing function parameters that should not be modified; when describing fields of structures and associations that cannot be modified (including structures for describing device registers); as the recommended replacement for #define for numeric constants.

Proof: this keyword does not cost anything during execution, on some systems it saves RAM by placing it in ROM, allows the compiler to detect invalid data operations, gives the reader additional information, takes up little space (does not require a separate line) - in short, there are no reason to neglect this rule ./* 1 /

char const *gp_model_name =  “Acme  9000”;
int const g_build_number = CURRENT_BUILD_NUMBER();
char *strncpy (char *p_dest, char const *p_src, size_t count);
size_t const HEAP_SIZE = 8192;


Rule 3

Use the static keyword wherever possible.
Consequence: Use for ALL non-local variables and functions that should not be visible anywhere, except for the module that declares them.

Proof: it does not cost anything at runtime, improves encapsulation and data protection, protects a variable with a long lifetime from spelling errors, * 1.

static uint32_t g_next_timeout = 0;
static uint32_t add_timer_to_active_list(void)


Rule 4

Use the volatile keyword where you need it (but not wherever possible - translator's note).
Corollary: we use in the description of global variables that can be modified by the interrupt service routine; when describing global variables modified by more than one task; when describing the registers of external devices mapped to memory.

Proof: somewhat slows down the execution of commands with such variables (this is where the note comes from), but avoids hard-to-detect errors, gives additional information to the reader, * 1.

uint16_t volatile g_state = SYSTEM_STARTUP;
typedef struct {
...
} my_fpga_t;
my_fpga_t volatile *const p_timer =  ...


Rule 5

Comments should not be used to disable a block of code, even temporarily.
Corollary: Use the preprocessor conditional compilation directive; do not use nested comments, even if your dialect C allows it; Use a version control system for experimenting with code; Do not leave commented-out code in the release.
It seems that the rule is clear and uncomplicated, but the hand reaches out to insert / * and * / (all the more so since the other rule - comments with // I already accepted) - we must fight the bad habit.

#if 0
a = a + 1;
#endif


Rule 6

When the length, in bits or bytes, of integers is significant for the program, use data types with a specific length (C99 types).
Corollary: The keywords short and long should never be used; The char keyword is used only to define strings.
Proof: costs nothing at execution, facilitates portability, * 1.

#include < stdint.h >
uint16_t data;


Rule 7

Never apply bitwise operations (&, |, ~, <<, >>) to signed types.
Consequence: You do not have to examine the vague behavior of these operations.
Proof: costs nothing at execution, facilitates portability, * 1.

Rule 8

Never mix signed with unsigned types in expressions or comparisons.
Corollary: Unsigned digital constants must have the u postfix.
Proof: nothing costs execution, you don’t have to remember the rules of type casting, * 1.

int s = - 9;
unsigned int u = 6u;
if (s + u < 4) {
// эта ветка НЕ будет исполнена, хотя -9+6=-3 < 4, как мы могли бы ожидать
}
else {
// А вот эта ветка - будет
}


Rule 9

Instead of using parameterized macros, inline functions should be used.
Consequence: in combination with rule 2, it excludes the use of #define a little less than completely (compilation options remain).
Proof: macros are not debugged in principle, and this is a very good argument, there is no type control when calling a macro, side effects are possible when modifying a parameter, but some C compilers perceive the inline directive as a wish, and not as a guide to action - as a result, execution time may increase. However, it is highly recommended for use.

#define SQUARE(A) ((A)*(A))
inline uint32_t square(uint16_t a)
{
  return (a * a);
}


Rule 10

We declare each variable on one line.
Corollary: The comma is not valid in the variable list.
Proof: you don’t have to remember how it works *, the readability of the code improves.
Nevertheless, in my opinion, the most ambiguous rule, since it increases the visible size of the code. Even if the declarations are compiled at the beginning of the module (and there are also recommendations to declare local variables immediately before use), I personally cannot attribute the extension of the code to the best sides, so I would continue to use declarations like:

 int i,j,k;


But of course, the following line is not acceptable:

 int *pi, pj;


To summarize, we can say that we have a way to slightly shorten our existing rope, while not hindering us to the impossibility of movement at all. Of course, one should not get to fanaticism in following the recommendations, one just needs to keep in mind that here, as in the Charter, each line is written in the blood of people who did it their own way. Moreover, none of the rules contradicts common sense, rather, they sometimes confront with ingrained habits, it is not at all necessary that rational.

Also popular now: