Asserts are a common tool in the development of software. They are used to validate "stuff" in our programs, and they can be quite useful to trap bugs when you've got a team of people working on a single piece of software. The humble assert macro evaluates an expression and if that expression evaluates to false (i.e. the assertion doesn't hold), it takes some action to indicate the error condition and give clues as to what caused it to occur. Having an enormous number of asserts in your codebase serves not only as documentation for the functionality by indicating the expected conditions at that point of execution, but they also serve as living tests that your code is actually maintaining those conditions as it executes.
Charles Nicholson over at Power of 2 Games wrote a post some time ago on their blog, describing (and making available) a very cunning implementation of this macro, and he spends a good number of words on the way to construct such a thing so that it can behave exactly as you want it to. If the dear reader has not ingested all that Charles has to say on the topic, I wholly suggest that if this topic interests you, you do.
Now, since the implementation of the assert mechanism is now a solved problem (thanks, Charles!), I feel that it is time to discuss ways to most effectively use them.
So, what are effective asserts? Asserts that are effective are asserts that tell you:
- when they are failing
- why they are failing
- where they are failing
Ok, obvious enough, eh?! So what's the point of this post? Well, we're reaching back to the definition of assert itself, with the goal of better defining when it is appropriate to use the macro in the first place. WordNet, via Dictionary.com, says that assert means 1) to state categorically, and 2) to declare or affirm solemnly and formally as true. What this means is that the assertion (i.e. the expression inside the assert macro's parentheses) is not saying, "this should be the case." No, quite a step away from that, the assertion says, "this is the state of the environment at this time." Yes, indeed, anything to the contrary indicates a logical failure in the application. The program cannot be allowed to continue in the face of a failing assertion for this reason alone. Taking a no-nonsense stance on this, it means that a program with one or more assertion failures cannot be executed further until the cause of those failures is addressed.
In practice, what does this mean? In the simplest form, assertions validate programming logic. Typical usage is to validate preconditions for functions, although validating postconditions is certainly useful as well. However, it is important to keep one's eye on the fact that the use of assertions should be restricted to logic errors. It doesn't make (logical) sense to assert that the computer has a mouse connected, or a display, or even that a given file exists on the hard drive and has appropriate contents. Even if the program requires all of these things, their non-existence or invalid contents are not a logical failure of the program. Looking at the default assert implementation on many systems, provides a view into the only valid thing to do in the face of assertion failures, and that's to call exit(). After all, if you know that the program is in a logically invalid state, what else can you expect to accomplish?
Calling exit() in the face of a missing data file, however, is less than user-friendly. Indeed, any errors that occur outside of the program's control should be handled by and with a user-friendly system to display a message along with information on how to remedy the error condition. This is not the job of the humble assert macro.
Looking at this hard-line stance of "assert must kill the program in the face of failures" from the other direction, if the program can continue in the face of any given error condition, then the assertion itself isn't valid; the truth of the asserted expression not a requirement for proper execution of the program, despite its appearance in an assert macro implying as much. In a sense, it's like an out-of-date comment, the "requirement" put forth by the assertion is not a requirement in any way.
Assert is very handy to use to capture error conditions in your program. It is perhaps a little too handy, though, as it often falls into use as a general-purpose "tell the user something" mechanism. At best, this leads to frustration on the development team ("I don't care if a mouse is attached for the work I'm doing, so why am I getting all of these pop-ups?"), and at worst it leads to confusion about how the system works ("this assertion says the pointer is non-null, but the code explicitly and correctly handles the null case?"). Clearly neither end of this range is desirable, so it's best to keep asserts for the things that the term itself implies, statements of truth.