« Archives in September, 2011

Habits for Safer Coding

bear.jpgIf you have been coding in C and C++ for some time, you know that it is easy to introduce bugs: type in a single wrong character and you wreak havoc. It is therefore a good idea to avoid using certain (dangerous) language features or at least using them in a way that is safer.

My favorite example is always putting braces around blocks, even if the block only consists of a single statement:

if (retries <= MAX_RETRIES) {
    enableOutput();
}

I've witnessed cases where really experienced programmers omitted these two "redundant" characters for brevity only to be later faced with this classic bug:

if (retries <= MAX_RETRIES)  // Added today.
    enableOutput();          // Added today.
    restartEngine();         // Added two weeks later.

It is so easy to make this mistake -- especially during merging -- that MISRA (Guidelines for the Use of the C++ Language in Critical Systems) made curly braces around if/else/do/while blocks a "required rule". Note that by following this rule, you are also protected from "dangling else" mistakes:

if (a < b)
    if (c < d)
        foo();
else    // This else actually belongs to the inner 'if'
    bar();

In general, I follow such "best practices" if they meet these criteria:

- They must solve a real problem
- They must not require any thinking

The "always-put-braces-around-blocks" rule does solve a real problem: I'm not aware of any compiler (not even Sun's Java compiler) that warns you about the mistake shown above. (Of course, static code analysis tools (like PC-Lint) do check for these errors, and many, many more, but unfortunately only few developers use them.)

Does it require any thinking? I don't think so. The only thing I have to remember is this: whenever I use 'if', 'else', 'for', 'while', 'do' I put curly braces around my code. That's all!

Maybe you have come across this convention:

if (MAX_RETRIES == retries) {  // Constant comes first.
    ...
}

Advocates of this style reason as follows: in C/C++ you can easily -- accidentally -- use an assignment operator when you actually wanted to use an equality operator. Since you cannot assign to a constant, the compiler will refuse to compile your buggy code:

if (MAX_RETRIES = retries) // Good: caught at compile-time.
if (retries = MAX_RETRIES) // Bad: not caught.

I must admit that I've never liked this convention. One reason is that it looks unnatural. Another is that it lacks symmetry; that is, it is not consistent with other comparison operators. What if you need to change the comparison some day to also include values greater than MAX_RETRIES?

if (MAX_RETRIES <= retries)     // Looks strange.
if (retries >= MAX_RETRIES)     // Looks natural, but requires an
                                // argument swap.

Or what if MAX_RETRIES is changed from a compile-time constant to a "normal" variable whose value is obtained from a config file? Do you swap then? In fact, this convention doesn't provide any help for mistakes in variable/variable comparisons, which occur also very frequently in day-to-day programming:

if (cfgMaxRetries = retries) // Bug!

For my taste, the utility of this convention is rather limited and it requires too much thinking: "If I compare a compile-time constant (or a literal) against a variable using the equality operator, I put the constant first". Isn't this a clear hindsight rule? It's a bit like attaching a tag saying "always lock the door" to your key.

Why not, instead, turn this into an habit: "If I do an equality comparison, I double check that there are two equal signs". That's not any more difficult to adopt, doesn't impair readability and has the additional benefit of working in all cases -- not just when comparing against constants.

I'm prepared to follow even conventions that look funny if they solve a real problem but I don't think that this convention does: today, most compilers produce warnings when you make this kind of mistake (for GCC it's -Wparenthesis, which is part of -Wall).

In C, there are numerous ways to make silly mistakes (in C++ there are many, many more). Instead of using questionable practices that address one tiny area, it is much better to enable all compiler warnings or -- even better -- use static code checkers like PC-Lint. In my opinion, that's the most worthwhile habit for safer coding of all.