Programming Style with Asserts
From Software Wiki
Once you have written a function, review it looking for assumptions you have made. Consider replacing certain comments by assertions. (Think of assertions as “executable comments”)
Document these assumptions in the form of..
- precondition “require” asserts
- Compile time asserts. (Very useful for catching portability “gotchas” on word sizes and the like.)
Review the function looking for ways in which it could go wrong, Asserts within the code document how you believe the algorithm should work.
Unless the reason for the assert is obvious add a comment explaining why this constraint exists or assumption is necessary. Unless you do so, the next programmer (or yourself six months later) on meeting a failed assert may blame the assert instead of themselves!
Contents |
[edit] Programming Style Downstream of an Assert.
Suppose you have asserted that a certain condition cannot happen.
How do you code after the assert?
After all, sometimes the asserts will be turned on, sometimes they will be turned off. Sometimes they will trigger a reset, sometimes they will just log the event and fall through.
An Assert Failure means there is a software defect fixable only by a new version of the firmware!
So the answer is simple.
The Certainty Principle
Prior to the assert you code as if the verity of the expression is in doubt. After the assert you code as if the expression was an absolute unquestionable certainty.
To have code that checks for that condition after the assert is undoubtedly a waste of lines of code, CPU cycles, ram and rom and makes the system less reliable not more.
Non-Redundancy Corollary
The body of a routine should never test for the routine's precondition.=== Unhandled events. === Some asserts merely document the design decision not to handle a certain eventuality. However we do not know whether or not the control flow will emerge from the assert or not.
assert( cantPossiblyHappen()); doStuffThatWillCrashIfItHappened();
Thus if our expectation is that if it does happen, we don't do anything and don't crash, then this code is buggy.
An attempt at correct code might be...
if (cantPossiblyHappen()) {
assert( false);
return;
}
doStuffThatWillCrashIfItHappened();
But what if someone sets assert to mean “system error and halt”? The correct code doesn't use assert.
if (cantPossiblyHappen()) {
log(“Unexpected event occurred”);
return FAILED;
}
doStuffThatWillCrashIfItHappened();
[edit] Programming Style Upstream of an Assert.
So if the assert expression is in doubt prior to the assert, how should we program upstream of the assert? Should we place checks on everything at every level above the assert?
The critical point here is the notion of Trust Regions. On entry to a Trust Region we need to decide what sort of exceptional condition is implied by invalid data. If the answer is “not a programming bug”, we need to check and take the action appropriate to that exceptional condition.
Inside the Trust Region we assume all appropriate checks have already been made.
ie. Inside the Trust Region the difference between the before and after assert behaviour is subtle and small.
Prior to the assert we need to consider whether all appropriate checks have been made, and if not make them at the appropriate data flow entry point to the Trust region.
After the assert we act as if it is unthinkable to be otherwise.
Asserting something hasn't happened isn't “Handling that Eventuality”. It's merely explicitly documenting our assumption that it won't ever happen.
[edit] Robust coding practices may mask errors.
Use asserts to make them explicit...eg Many programmers will rather do...
while( i < N) {
....
}
instead of
while( i != N) {
...
}
since it defends against the possibility that i may step past N creating an infinite loop.
A slightly better style is...
while( i < N) {
....
}
assert( N == i); // This code assumes
// i should start < N and then single step.
[edit] When do you remove Asserts?
Asserts should remain in the code for the full life of the product.
Do not remove them. If you need to remove one for performance reason rather change it to an assert variant that is not run at at production debug level. Or at worst, just comment it out.
Be it but one line of code, some day it must be maintained.
[edit] Use the right assert.
By choosing the right assert from the pallet of available asserts, you allow for creative optimizations without loss of value.
Use a “require” assert for preconditions and an “ensure” assert for postconditions. This enables you to selectively disable postcondition asserts if need be.
If your system has throws an exception on a null pointer access, why bother to assert that the pointer is not null? Trust to the hardware and the exception handler to do The Right Thing.
However,
- This is not portable coding.
- It loses the documentation value of the assert.
Thus instead of...
assert( pointer); // or.... assert( pointer != NULL);
use...
assert_valid_pointer( pointer); // or assert_heap_pointer( pointer);
This permits the system architect to elect to trust to the hardware, or create an assert that checks that the pointer points to a valid address. Furthermore, the heap is probably a very specific region of memory. Thus “assert_heap_pointer” is a much tighter constraint than p != NULL.
Say What you Mean!
Create a pallet of asserts that will allow you to express your intention.
[edit] Compile Time Asserts Are Better Than Run Time Asserts.
Why wait until the customer gets it? Fail asserts at compile time if you at all can.
Good candidates for compile time asserts are to document non-portable assumptions about word sizes, endianess and non-standard compiler features.
However, better than compile time asserts is to use the C99 standard <stdint.h> predefined typedefs to select the appropriate word size.
Other candidates are library version numbers and constraints on sizes of statically allocated resources.
Previous: Assert failures Next: Implementing Asserts
