Debug.Assert Considered Pointless
One thing I was told as a young programmer was to make good use of assertions for checking code. As time goes by however, I can see less and less use for assertions. I’m starting to think they’re pretty useless.
(I’m going to use C# in my examples here: some of these points might not apply to all languages. By default, an assertion will not fire in a C# release build, and furthermore, the code inside will not even be executed).
Update 2009-06-11: I probably haven’t been clear enough, but luckily various commenters have picked up on this: I’m not advocating removing assertions and replacing them with nothing! In general, I think assertions should be replaced with a stronger check that also runs (or can run) in a release build.
1. Pointless Guard Checking
Unfortunately in my coding travels I see quite a bit of code like this:
void Foo(Bar bar)
{
Debug.Assert(bar != null);
bar.DoSomething();
}
What’s the point? If bar is null, then using it will throw an exception anyway. Why add unnecessary lines of code that make reading harder?
2. Bad Argument Checking
But, you’re thinking, what if the usage is separated from the assignment?
class Foo
{
public Foo(Bar bar)
{
Debug.Assert(bar != null);
_bar = bar;
}
public void Method()
{
_bar.DoSomething();
}
private Bar _bar;
}
True, you want to catch the error as it happens, not later on in the method. But shouldn’t you be throwing an ArgumentNullException instead?
3. Difficult Detective Work
So now you’re telling me that you’re not writing component code, and the only client of this class will be the application it’s embedded in. Why not save some effort and skimp on the checking code, and make it debug-only?
Fine idea… until you get a crash report from one of your users, and the stacktrace points to a NullReferenceException in Method(). Now you have to figure out where the null value got passed to the constructor… a potentially much more difficult task.
4. Differences Between Debug and Release
If you’re like me, you generally run the applications in debug. Your customers however, run in release. Why make the differences between these two versions any bigger? Even if you don’t intend your debug statements to change program state, it’s all too easy to accidentally do so:
- Running lazy-initializers or singleton constructors;
- Loading data from the database via an ORM;
- Moving data in or out of a cache;
- Running static constructors.
5. Too-Shy Sanity-Checking
Sometimes, despite the above warning, you really do have sanity-checking code that you’d like to run in a development or test environment to catch regressions, but which is too slow to give to general users. (I read somewhere that Excel has two computation engines: a simple, slow, reference one, and a fast, parallel, release one. Testers can run the two in parallel, and any differences are automatically flagged as errors).
So you might be tempted to do something like:
Debug.Assert( SlowlyAndCarefullyVerifyInternalState() );
The problem I have with this is that the debug/release division is way too blunt.
- Suppose a tester wants to test a release build, but also perform the sanity-checking?
- Suppose a customer finds a bug in a complex calculation, and you want them to run the checks?
- Suppose a developer wants to debug performance issues in the non-checked code?
I would suggest you move the conditionals for code like this to a (possibly hidden) configuration setting. Someone wants to run in slow-and-safe mode? No problem, just tell them the registry setting (or whatever). By all means, change the splash screen or title bar or whatever so it says “test mode”.
The advantages of this method are:
- Anyone can turn it on and off;
- Sanity-checking can be controlled at a much finer granularity than all-on / all-off.
6. Is It Recoverable, Or Not?
The final argument against debug assertions concerns just exactly what an assertion means.
To my mind, an assertion is different to a simple flow-control test (“did the user enter a valid number?”), which is something the program can (should) handle. By definition, an assertion is checking for a disallowed state. An assertion doesn’t check for incorrect data; it checks for an incorrect program. Continuing after an assertion leads to undefined behaviour.
So how can you continue? More to the point, why should a user even be allowed to continue? If the state really is unrecoverable, then fail. Fail fast, log the error, and bail (possibly trying to allow the user to save work, or whatever). Having a release-build application continue after what would have been an assertion is just plain wrong.

or defer validation at it’s destination (e.g. database).
If you’re already handling exceptions (as you must in .Net, for instance) then throwing an exception may be the proper thing to do.
But there’s still a lot of C and C++ code out there that does not throw exceptions. In my co’s case, we had a library product where we wanted to avoid forcing users to use exception handling — at least at the time, a lot of people (rightly or wrongly) felt strongly about potential overhead, etc.
There are also times you want to be able to perform addl checks that are simply too expensive for a release build — for example, google on _GLIBCXX_DEBUG use with gcc.
Short version is that assertions can be very valuable, but should not be the only (or even the first) line of defense against bugs.
Interesting.
I wrote a whole section about assertion checks in my Principles of Software article, http://wiki.freaks-unidos.net/weblogs/azul/principles-of-software#assertion-checks. I agree with most of the points you raise, indeed I said: “You should only disable your assertion checks if it is strictly necessary (for example, for performance reasons). I almost always run my software with assertion checks enabled.” I also wrote some thoughts about how slow it is acceptable to make your checks.
I’ll probably add a link to this article in that section. I’ll probably update it a bit to mention some of the ideas that you have here (I like the idea of a config switch that can enable or disable expensive tests).
One small suggestion: if one only skims through your article, it would at first seem to recommend that programmers don’t explicitly validate the state of their processes to protect against invalid programs. However, I think that couldn’t be further from your point: your point is actually that they should check it with better, “stronger” (so to say), mechanisms that Debug.assert. Perhaps you should rephrase the starting paragraph to say that you think that validating the state of your program is too important a task to use Debug.assert.
Regarding Excel, you read that in “Debugging the Development Process” by Steve Maguire.
Point 1 is like overcommenting. It seems like an assert here is not needed, as would be a comment like “#do something with bar”.
I find asserts useful for
a) Documenting assumptions and preconditions. For example, you could add an assert to show that you never expect to get a NULL. Otherwise, two years from now, someone is going to wonder whether ignoring NULL was on purpose or a bug.
b) Helping develop new code. For example, if loop over a list for an item that I know is in it, I might add an assert to make sure my index was within the bounds:
for (i = 0; wanted != items[i]; i++) {};
assert(i <= numberOf(items)); # wanted must be in my array of items, right?
Then the question becomes whether that assert should ever be removed.
Would you say that too many asserts is a bad thing?
Dion: In the example you give, I think the assertion should be stronger: it should throw an exception in both debug and release. If it’s invalid in debug, it should be invalid in release too.
Too many asserts… yes a bad thing. Qualified: so long as they are replaced with something else. And, of course, “too many” is of course a value judgement.
FFS… I came across this evening running a webservice in debug mode from another VS instance – they aren’t evaluated even though they step through. I can’t believe that there is no way to enable these checks, especially when they are effectively validation statements in there own right!! Now I’ve got to rewrite all of them as I want validation checks! Pathetic as it disrupts code behaviour and gives you a different flow of execution!