Team LiB
Previous Section Next Section

"But I Always Test My Code, So Why Do I Need Asserts?"

Even with thorough test cases, some subtle bugs might not be apparent unless you carefully study every single piece of output. You might not even notice the bug until after shipping. But if you assert every little assumption your code makes, then you'll have that much more warning if something goes wrong. Asserts take a conscious effort to ignore, and that's the point. Every time your program runs, that darned assert will pop up, reminding you to fix this bug, and the constant prompting encourages you to fix the bug sooner rather than later (if only to stop the annoyance of having to dismiss the assert each time).

Besides, even if the existence of a bug is obvious, the root cause of the problem might be occurring well before the symptoms manifest themselves. We don't want to spend time backtracking from the point of failure to the original cause. We want to launch the debugger on the very first warning signs.

Finding the Point of Failure

One day, my friend Amy asked me to help debug some scientific measurement software. Data from physical sensors was automatically fed into a complex embedded spreadsheet, and based on those calculations, feedback to the system was introduced. But over time, a subtle bug somewhere caused the values in the spreadsheet to become inconsistent in a barely perceptible error that magnified itself with each iteration until the entire system crashed several hours later. Debugging this was extremely difficult because the actual bug was happening long before the symptoms manifested themselves. Stepping through the literally millions of iterations required for the crash would simply take too long.

I believe you can never be too rich or have too many asserts. So I helped my friend write a simple function that examined every single value in the spreadsheet to ensure the data was consistent. Of course, running this function took a long time. Then we found the ten places in her code that modified that spreadsheet, and after each one, we added our assert:

Debug.Assert(SpreadSheetIsConsistent());

We recompiled in Debug mode, started the system again, and went home for the day. When we came back, an assert message was flashing on the screen. Now the code was frozen at the exact place where the data inconsistency first appeared. We had a stack trace and variable values and we now knew exactly which iteration was responsible for the initial inconsistencies in the data. Armed with that, fixing the bug was trivial.

What was the cost of adding those asserts? Well, in Debug mode, the performance hit was pretty drastic. The Assert function itself is fast, but the condition we were asserting on was slow. Validating the entire spreadsheet after each of the millions of iterations required huge amounts of processing time. But so what? Debug mode is for debugging—you can afford for the program to be slow then if that'll make your job easier.

The question is, what is the cost of asserts for the "real" version of the software that gets compiled in Release mode? And the answer is, absolutely no cost at all. Remember, debug asserts only run in Debug mode. The code inside the assert will be compiled away to nothingness in Release mode. So adding these debug-only asserts not only reduced a 3-day debugging task into a simple exercise, it didn't even cost the final product any performance at all.

This highlights an important lesson. Some developers think you should never write a debug-only assert that isn't also handled in Release mode. That is, some developers think we should have done something like this:

Debug.Assert(SpreadSheetIsConsistent());//Debug mode only
if (!SpreadSheetIsConsistent()) {//Runs in Release, too
   //Handle error
}

When performance in not an issue, that philosophy is definitely true. (See the sidebar "Asserts Are Not Error Handling.") If verifying data in Debug mode is good, then verifying data in Release mode must be good, too. But when performance is an issue, then that idea may be compromised. Such was the case here: Although we didn't care about performance in Debug mode, the SpreadSheetIsConsistent function was too time consuming to run a million times in Release mode. In cases where the validation might noticeably slow the product down, you have to be more aggressive with your validation checks in Debug mode than in Release mode. In every other case, though, you should have both error handling and asserts.

Asserts Are Not Error Handling

I've seen many developers declare that asserts encourage poor code by tempting programmers to skip writing error handling code. If so, that's a problem with lazy programmers, not with asserts. Some amateurs do indeed use asserts instead of error handling. These developers mistakenly believe that when the code runs without any asserts, there are no errors left, so why bother handling errors that will never occur? Programs developed in this fashion tend to crash-n-burn hard when confronted with the unexpected conditions of the real world. That's why some developers think asserts encourage sloppy code.

The developers who use asserts in this lazy way are half right. They litter their code with asserts and then test, knowing that asserts are a fantastic way to quickly identify and fix bugs. When your program runs in your development environment without asserts, you can indeed feel confident in it. But these developers don't realize that they'll never find all the bugs. That's why you still need error handling, too.

Asserts have absolutely nothing to do with error handling. They are not an alternative to error handling, nor a supplement, nor a special case. They serve an entirely different purpose.

Side Benefits of Asserting

Even after fixing the spreadsheet bug, we left the asserts in the code. They may be useful in the future if somebody introduces another, similar bug. Besides, the presence of those asserts gives us both a shot of confidence. When the Debug program runs and we don't see an assert, then we both feel more secure that the code is functioning correctly. In fact, that's a trick I've often used with great success. Ever see a bug that only manifests itself once in a while and you can't quite determine the pattern? Throw in a bunch of validation asserts all over your code and wait. Sooner or later, an assert will pop up to identify the bug at its root cause, which is way better than identifying the bug at its later symptom.

Naturally, if you use asserts heavily, you'll eventually assert on some condition incorrectly. Maybe when writing the code, you asserted a certain thing would never happen, but later on that assert fires on what you then realize is a perfectly valid case. So that particular assert is wrong and needs to be removed. That's another reason some developers don't like asserts: because you'll occasionally make a mistake and write an assert that shouldn't be there. Well, so what? As bugs go, an inappropriate assert is very benign. Aside from mildly annoying the developer, no harm is done to the product. Just remove that assert.

Besides, that incorrect assert taught you something about the code that you didn't know when you were writing it. The assert forced you to realize that the unexpected case was acceptable in this function. That's good information to have. Now you should review the code to make sure it can correctly handle this unanticipated case.

Don't Remove Asserts Just Because They're Annoying

I can't count the number of times a team member has told me, "Your code is asserting and it's really annoying. Can't you take those asserts out?" We look at the code and see the assert was exactly right: My teammate was doing such-and-such wrong. In fact, the assert message even clearly said in plain English what the problem was—that's the entire point of using asserts. So we fix the guy's code and the asserts go away. But then he says something like, "Well, OK, I see. But can't we comment out those asserts anyway so that they don't bother us again next time?"

I always take the time to politely explain that we should leave the asserts because they're helpful when tracking down bugs. But I privately think, "You invoked my code in an incorrect way; but rather than making you spend an hour debugging, my code automatically told you exactly what you were doing wrong before you even noticed there was a bug at all, and yet you want to remove that safety net?" It's the ostrich effect— developers cringe at asserts because it proves they have a bug that they can't ignore. Whereas if there were no assert, maybe nobody would notice the bug until after the code shipped. Then they wouldn't have to deal with it because that angry customer will be somebody else's problem.

Don't be like that. If someone went through the trouble to write an assert, they probably had a good reason for doing it. Respect that reason and assume any assert you see is telling you something important.


Team LiB
Previous Section Next Section