Vocabulary
First off, we need to agree on a common vocabulary. Language is how we communicate, and it only works when we all agree to the same meaning for the words. Dictionary.com offers this as a defintion for assert: “to state with assurance, confidence, or force; state strongly or positively; affirm.” That’s good background information, but in the context of C++ we have a slightly more specific meaning. To avoid confusion when speaking to C++ programmers, we need look no further than <assert.h> for the definition we seek.
Simply put, assert is a macro which evaluates an expression and halts the program if the expression is false. The name of the file as well as the line number where the assert can be found are provided as output.
Yes you knew that already didn’t you? Of course you did. We all know what an assert is. Yet why do I keep hearing the phrase, “non-fatal assert?” We’ve agreed that an assert will halt the program. That’s what it does. So what would be the meaning of an assert that does not halt the program?
It is very reasonable to want to use some of the features of the assert macro for debugging purposes in a way that would not require the program to stop. Perhaps there is some sort of non-fatal glitch you want to monitor, but you don’t want to interfere with everyone else’s progress. You would like a macro that evaluates an expression and outputs the filename and line number if that expression is false but does not halt. This is a great idea. I would suggest that we refer to such conditions as warnings so that they are not confused with asserts.
Asserts are Not Break-Points
Where people start to really go wrong with their asserts is when they overuse them. Many years ago, I was working on a game with one other engineer – a perfectly competent one at that. One day a designers came to me because his game was crashing every time he tried to go to a particular area of the map – naturally the area where he was working that day. I tried it for myself and found that the crash he was experiencing was actually an assert. With only the 2 programmers on the team, it wasn’t too hard to find the right guy to ask about this. When I pointed it out to him, he replied, “Oh, that’s just there for debugging, you can go ahead and step over that.”
Seems he was looking for some sort of bug, and he was using asserts sprinkled all over to try to catch it. Clearly this bug wasn’t so severe that it was going to cause major problems without the assert because he had just told me to skip over the assert.
I should not have to say this, but this is not the correct way to use asserts. In this case, the programmer had managed to turn a minor bug into a progression-blocker that had already wasted multiple man-hours of designer time not to mention the time I had also wasted on it already.
Do all of your artists and designers run with debuggers open so they can “step-over” your asserts? Yeah mine don’t either. Do the other programmers on the team know which asserts are important and which are just your own personal experiment? How would they?
Do Not Allow Asserts to be Skipped
As I said, this example shows how the really catastrophic assert abuse gets started, but that’s only the beginning. What happens next goes something like this…
- There are asserts in the code which are otherwise non-fatal (such as in the above example).
- The rest of the team must not be blocked by innocuous asserts.
- We must therefore set up a way for asserts to be skipped-over!
Brilliant! Oh, and you thought you were a genius for coming up with that one first right? Well congratulations, you’ve just broken asserts. Once you implement this lovely new feature, asserts will immediately start becoming more and more useless.
Don’t agree? Here’s how it goes. Once asserts can be skipped over, they are no longer a progression blocker. This gives you a license to put asserts in wherever you want to with no worries that it will prevent anyone from getting their work done. So what happens next is asserts start going in where warnings belong. The team will start using asserts to warn artists and designers about mistakes they seem to have made:
assert(pFrog->pLilyPad); // the frog must have a lily pad! assert(hitPoints > 0); // monsters must have hit points! // make sure your triangle is not degenerate! assert(vertex[0].x != vertex[1].x || vertex[1].x != vertex[2].x || vertex[0].y != vertex[1].y || vertex[1].y != vertex[2].y || vertex[0].z != vertex[1].z || vertex[1].z != vertex[2].z);
Ok so I made up the lily pad bit, but the degenerate triangle is one I’ve actually run across before.
Still don’t see the problem? The designers get warned every time they create a frog and forget to give him a lily pad. Sounds great right? In practice, you don’t have a serious problem yet. Like hair in your shower drain, it takes a while for the clog to really build up.
The way it happens is so simple, it’s almost hard to believe it could become a problem. One day an artist is working happily along and he gets an assert because some frog doesn’t have a lily pad. “Not my problem,” he says to himself. He quietly hits the “ignore it” button and moves on. And then he ignores the next one, and the next until before long, there are so many of them that he no longer even pays any attention to them at all. Now he will silently click right past the assert that was intended for him to see and take no action to fix it.
There’s a certain critical mass of error messages that gets reached at some point, and once you hit it, all error messages become just another line of spam to be ignored as they scroll past glazed-over eyes. This critical mass is different for everybody, but the key factor to remember is that it is invariably lower for the end-user than it is for the programmer. For many non-technical people, I would guess this critical mass may be as low as 2 errors per run. In fact, if a non-technical person becomes accustomed to seeing 2 errors every run, I would hazard a guess that it would become very unlikely that they would even notice a 3rd error should it suddenly appear. Once you’ve added that “ignore it” button, that’s exactly what will happen – people will start to ignore your errors.
If you’re one of those people who still doesn’t see the problem, you’re probably thinking something like, “It’s not my fault if people ignore errors that are right in front of them. They need to pay attention to those.”
If you’re still thinking that way, then consider this: Just how bad could it get?
How Bad Could It Get?
Imagine the scenerio I described is allowed to go unchecked for a little while. Imagine a team of 100 (30 of whom are programmers) working together for 2 years under the philosophy that every conceivable error will have an assert on it. Now imagine you are dropped into the midst of that team. Perhaps you are a new hire, or maybe you are a temporary contractor brought on for some last-minute optimization. What might this project be like?
I’ll give you a real-world example. Without naming names, I have seen an actual video game project where booting into the first level of the game from a cold start results in 17 unique asserts with another 11 over the course of playing that first level. I tried to count the total number of occurances, but after getting 20 instances of the first assert, I gave up and punched the “ignore this assert” button. Hard to believe? This is a true story, and I’m not talking about early in development. This project was post-beta at the time I performed this test.
So when you find yourself dealing with this, what’s the first thing you do? That’s right – you figure out a way to disable asserts altogether. Only now you find yourself asking, “what do I do when I actually want an assert?” Well you’ll probably eventually break down and create a new assert macro. Maybe this time you’ll call it “crash” because the assert doesn’t crash anymore, and you want to be sure yours does. In fact this same project I’m talking about already has a crash() macro. I’ll give you one guess what that macro does. If you guessed “it crashes,” sorry, but thanks for playing. Once upon a time it crashed, but at some point, the brain-trust who decided that asserts should be skippable came along and “fixed” crash() too. On this project, crash() merely logs the event to a text file and happily moves on without a blip – completely unnoticed.
Improved Asserts
Don’t get me wrong, there are ways to legitimately improve the assert macro. Just remember not to alter the fundamentals we agreed on for the definition of “assert”.
- Evaluate an expression
- Halt the program if the expression is false
- Provide the location of the assert
So what are some ways to improve the simple assert?
- Warnings The first thing you should do is create that warning() macro so that people are not tempted to use asserts for non-fatal issues. I urge you for all of our sakes please do not call your warning macro assert_nonfatal. Don’t perpetuate the idea that asserts can in any way be non-fatal.
- Messages The standard assert will echo back only the expression, the filename, and the line number. These are likely to mean absolutely nothing to the end user. Adding a meaningful string to the assert will help the team bring it to the correct programmer’s attention faster. I like to use a little macro assert_msg(expression, message) for that.
- Author These asserts must be addressed by programmers – you aren’t using asserts to remind designers to give their frogs lily pads right? So the only important part of the assert message is which programmer needs to know about it. I know of one company where they have created special assert macros with the initials of the programmer on it like assert_mrw(expression). When one of these goes off, it tells the victim which programmer to go talk to.
- On-Screen Displays I mostly work on consoles, and some consoles don’t provide text output outside of the debugger. What that means is artists, designers, and even testers will get an assert and be unable to see the text. The assert is there to provide valuable information about the crash, so in these situations, it is worth the effort to print the relevant information on the display in addition to the text output before breaking.
- Unconditional Asserts I see these all the time, and to be honest they bug me, but there’s technically nothing wrong with them so enjoy yourself. If you’d prefer to type assert_unconditional() instead of assert(0), well that’s your choice. You won’t catch me doing it.
Summary
Asserts are really very simple as long as we all agree on what they mean and use them correctly. If you maintain a strict difference between a warning and an assert, your project will run smoothly.
You may still be sitting there saying, “this will never happen to me because we won’t let it get that bad.” Maybe you’re right, but I think it comes down to a matter of philosophy. If you have a philosophy at your organization where it’s ok to misuse asserts, you should expect them to be misused.