8.1.5. How to Use assertions

When developing it is sensible and good practice to use assertions in order to verify conditions that should be true. The following guide summarizes the different styles of assertions that are available in this codebase. #error directive

The C preprocessor allows to fail directly in the parsing of the C-files. This is useful for cases such as when a certain configuration through defines is not supported. Generally, defines should be used as less as possible.

Listing 8.10 Using #error directives
#define NUMBER_OF_XYZ 3
#if (NUMBER_OF_XYZ > 2)
#error "Configurations with more than 2 XYZ are not supported"

The downside of using the error-directive is that the C-preprocessor runs before any compilation is done. Therefore it is impossible to e.g., check size requirements of variables. static assertions

Newer C-standards such as C11 have an assert that is able to do assertions during compilation. It is called static_assert. We have emulated this function with a construction that will fail during compilation if the condition is false. This allows to check for example the alignment of variables or that the size of a variable is not zero.

Listing 8.11 Using static assertions
/* db_variable is defined in your code and should contain something */
static_assert((db_variable != 0), "database may not be zero");
/* This assertion will not compile if db_variable has size zero. */ assertions

Whenever it is impossible to determine a condition at compile time, normal assertions can be used. In this project a macro called FAS_ASSERT(condition) exists. It supports bringing the BMS to a full-stop in case of a failing assertion. Please refer to the module on implementation and usage details.

Assertions should be used generously in your code in order to assert conditions that should always be true. Examples for this are:

  • The variable passed to a function is explicitly documented as being not null and your code expects this. Make sure with an assertion that no programming error has occurred.

  • The default case of a switch-case-statement is not expected to be reached in the program flow ever. You can add a FAS_ASSERT(FAS_TRAP) into the default case in order to trip when this defect occurs.

However, there also exist cases where an assertion is definitely not the right tool. Examples are:

  • A sensor value is expected to be in a certain range, but a hardware defect might lead to larger values. This should be handled by error-handling code.

  • The communication to a slave-board fails or returns an invalid CRC. This is also an error that is plausible to occur and not necessarily connected to a programming fault.

To sum it up, assertions should only be used for defects that are introduced by incorrect parts of the program. They should not be used for “normal” failures that might occur and just have to be handled gracefully.

Finally, care should be taken as code with assertions should be tested thoroughly in order to make sure, that no reboot loops or similar problems occur. Apart from that having assertions enabled in production code is OK, as they often catch errors that might lead to completely unexpected behavior.