Team LiB
Previous Section Next Section

What Are Asserts?

The idea behind asserts is so very simple. When writing code, you realize some condition is always expected to be true: A certain variable will always be greater than zero, for instance. If that condition is ever not true, then there is a bug. Of course, your code should try to handle the unexpected condition as gracefully as possible, but don't you want your program to notify you when there's a bug? You want good error handling so that the end user never even notices the bug; but at the same time, you yourself want to be informed of all bugs so you can fix them, regardless of whether they are handled or not. So in addition to handling the error, you also give yourself a warning message:


if (age < 0) {
   MessageBox.Show("Error: age cannot be < 0!");
   //Now handle the error as best as you can
   ...
}

That way, if the error condition ever occurs, the program will halt until you acknowledge the problem. After all, the code is in a state you thought it could never be in—either because of a bug or else because you didn't anticipate a legitimate condition. Either way, this condition is worthy of your attention. You don't have to fix the program right now—you could just hit the button to dismiss the MessageBox and let the error handling code take care of it—but you do have to at least acknowledge, "Yes, I understand my program is doing something I thought it would never do, and at some point I need to fix that."

The Problems with Message Boxes

Except there's a big problem with using a message box to display that warning message. While developing the code, you want all errors to be highly visible so you can notice and fix them before the code ships. However, your attitude changes once the code ships. You don't want to worry the customer with warning messages on every bug, so now you do want your error handling code to hide all bugs. Therefore, before shipping, you'll have to go through your code and remove all of those MessageBox.Show functions. That's a lot of work, and odds are good you may forget one. There must be a better way.

So how do you show highly visible warning messages to the developer without also showing them to the end user? One option is to write the warning messages to a log instead of displaying message boxes on the screen. But while logging errors is a good practice for other reasons (see Chapter 5), it's not so good for this problem here. Warning messages in the log are easy to ignore. Do you carefully scrutinize every line of your program's log after every single test run of your program? Few developers do.

The best way to catch these errors is to show an in-your-face message that's impossible to miss, and errors in the log are just too easy to ignore (or at least convince yourself, "Oh that's not important—I'll deal with that later maybe."). Besides, you'll only notice the messages in your log after the program run is finished. It's better to pop up something the instant the bug occurs so you can jump in with the debugger to figure out what's going on.

Solution: If we want a message box to show up for the developer but not for the user, and if we don't want the nightmare of maintaining two different versions of the code, then we can use conditional compilation to produce both a "Debug" and "Release" build. Unlike Java,.NET supports conditional compilation: the ability to run certain code only in Debug mode but not in Release mode. So we could write

#if DEBUG
   MessageBox.Show("Error: blah blah blah");
#endif

Now we have a single version of the code that can be compiled in two ways: The message box will appear in Debug mode, but not in Release mode. This gives us the best of both worlds: We only have to maintain one version of source code, but by changing a single switch in the compiler, we can produce a debug build for our own personal testing or a release build for formal testing by the quality assurance department and eventual release.

Tip 

Actually, .NET supports conditional compilation for any symbols you like, not just DEBUG and RELEASE. Just as in C and C++, you can define your own symbols: USING_VERSION_1_GUI and USING_VERSION_2_GUI, for instance. This feature can easily be misused, but it does allow great flexibility when you're not yet sure which direction the code is heading.

Writing #if DEBUG... #endif over and over grows tedious. Instead, we can use a shortcut. The.NET library includes a great helper class called System.Diagnostics.Debug. Inside that class, we simply call the Assert method to get exactly what we want: The message box will appear in Debug mode, but not in Release mode.

System.Diagnostics.Debug.Assert(x >= 0);

Using Asserts to Launch the Debugger

In fact, that Assert function is better than a message box in two other important ways, as well. A standard message box simply has a single OK button. It lets you see that an error occurred, but there is no option to launch the debugger at the point of failure. Nor does the message box show you how the code reached the faulty state. Wouldn't it be great if you could see a stack trace with function names and line numbers and everything? And wouldn't it be nice to have the option to ignore the problem or launch a debugger?.NET's Assert function fits the bill nicely (see Figure 4-1).

Click To expand
Figure 4-1: A failed assert

So when an assert pops up, investigating the problem is easy. The assert gives us a stack trace listing the exact line of the exact file, and we just look in that exact spot to see exactly what went wrong. This is nothing we couldn't have discovered by other means, but the assert presents the information automatically so we don't have to waste time gathering the information by hand. Even better, with a single click of a button, we can jump right into the debugger at this exact point in the code.

That's all an assert is. It's a debug-only warning to the programmer that something unexpected happened. An assert is just the programmer saying, "I think this condition will always, always, always be true. Anytime that assumption is not true, my code is behaving differently than I expected, and I want to be notified."

Asserts don't replace error handling and they don't replace logging. Instead, think of them as a form of documentation that gets verified at runtime. Rather than merely adding a comment that a certain variable should never be null, also add an assert. Your code might work fine today, but tomorrow a coworker will change something that invalidates one of your assumptions, and you'll have to patiently debug to figure out what broke. But if you assert every assumption you make, then a flurry of messages will fire as soon as the bad change is made. The asserts will clearly identify exactly what assumption has been violated, and then a fix should be easy.

Of course, it goes without saying that the reverse is true, too. Rather than merely adding an assert, you should add that comment, as well. Asserting that a variable will never be null is probably self-explanatory and doesn't need a comment; but asserting that a certain integer must always be in the range of 3 to 9 is much less obvious, and may require a comment to explain.


Team LiB
Previous Section Next Section