Discussion
Team LiB
Previous Section Next Section

Discussion

It's hard enough to find an error in your code when you're looking for it; it's even harder when you've assumed your code is error-free.

Steve McConnell

It is hard to overestimate the power of assertions. The assert macro and alternatives such as compile-time (and, less preferably, run-time) assertion templates are invaluable tools for detecting and debugging programming errors during a project's development. Of all such tools, they arguably have the best complexity/effectiveness ratio. The success of a project can be conditioned at least in part by the effectiveness with which developers use assertions in their code.

Assertions commonly generate code in debug mode only (when the NDEBUG macro is not defined), so they can be made "free" in release builds. Be generous with what you check. Never write expressions with side effects in assert statements. In release mode, when the NDEBUG macro is defined, asserts don't generate any code at all:



assert( ++i < limit );              // bad: i is incremented in debug mode only



According to information theory, the quantity of information in an event is inversely proportional to the likelihood of that event happening. Thus, the less likely some assert is to fire, the more information it will bring to you when it does fire.

Avoid assert(false), and prefer assert( !"informational message" ). Most compilers will helpfully emit the string in their error output. Also consider adding && "informational message" to more complex assertions, especially instead of a comment.

Consider defining your own assert. The standard assert macro unceremoniously aborts the program with a message to the standard output. Your environment likely offers enhanced debugging capabilities; for example, it might allow starting an interactive debugger automatically. If so, you may want to define your own MYASSERT macro and use it. It can also be useful to retain most assertions even in release builds (prefer not to disable checks for performance reasons unless there's a proven need; see Item 8), and there is real benefit to having an assertion facility that can distinguish between "levels" of assertions, some of which stay on in release mode.

Assertions often check conditions that could be verified at compile time if the language were expressive enough. For example, your whole design might rely on every Employee having a nonzero id_. Ideally, the compiler would analyze Employee's constructor and members and prove by static analysis that, indeed, that condition is always true. Absent such omniscience, you can issue an assert( id_ != 0 ) inside the implementation of Employee whenever you need to make sure an Employee is sane:



unsigned int Employee::GetID() {


    assert( id_ != 0 && "Employee ID is invalid (must be nonzero)" );


    return id_;


}



Don't use assertions to report run-time errors (see Items 70 and 72). For example, don't use assert to make sure that malloc worked, that a window creation succeeded, or that a thread was started. You can, however, use assert to make sure that APIs work as documented. For example, if you call some API function that is documented to always return a positive value, but you suspect it might have a bug, plant an assert after the call to validate its postcondition.

It is not recommended to throw an exception instead of asserting, even though the standard std::logic_error exception class was originally designed for this purpose. The primary disadvantage of using an exception to report a programming error is that you don't really want stack unwinding to occuryou want the debugger to launch on the exact line where the violation was detected, with the line's state intact.

In sum: There are errors that you know might happen (see Items 69 to 75). For everything else that shouldn't, and it's the programmer's fault if it does, there is assert.

    Team LiB
    Previous Section Next Section