Asserts are evil, except when you have no other choice

Noel Llopis over on “Games from Within” has written a nice rebuttal to my Asserts are evil post and the follow up.

I think it’s probably about time to wrap this up ;) So, here’s what I’ve learned…

[Updated: 24th October - there is an interesting discussion still going on over in the comments on Noel’s post…]

The reasons that I stated for disliking the use of assert in C++ were as follows:

  • The check will, likely, go away in a release build.

  • The assert is covering for poor design.

  • The assert makes it very hard to write a unit test for the failure condition.

  • If the object is running without a user interface then the assert’s dialog box may cause issues.

  • and finally: The assert is really just an “exploding comment”.

Something that many people who commented seem to have missed is that I was indicating problems with the traditional “standard” assert. Use of non standard asserts can deal with many of the issues. Most notably you can use a non-standard assertion to avoid issues 3 and 4 and, possibly, 1.

So, if you do feel the need to use assertions, ideally, you should be using something a little more refined than the standard assert. Perhaps Noel will post his code at some point; otherwise you can probably find something somewhere (if you know of any nice replacement asserts add the links to the comments on this article).

My main problem with assert is that sometimes it’s used when normal error handling code should be used instead or where the design is broken and needs fixing. What I’ve been reminded of by the discussion is that we all work in different environments and what’s good for some of us is not so good for others. As Noel points out, sometimes there isn’t time to fix the design and in these situations it’s better to use the “exploding comment” to remind you that you haven’t fixed it and sometimes all you can do is pray if the problem occurs in the release build. Personally I prefer that these tests stay in the release build, but I can see what many others want it to go away.

It seems like we’ve come full circle to Mark’s Ferrari versus Land Rover posting. In the kind of code I write I don’t ever want the program to continue onwards after a problem could have been detected; you don’t want a financial transaction to occur that is incorrect just because your asserts are compiled out. Likewise you don’t want a database update to occur with invalid data simply because an assertion didn’t fire in the release build. If a test stays in the code all the time then I don’t see it as an assert, it’s just normal error checking and it needs some form of handling even if that’s just to do the best you can to shut down cleanly. Often though it’s a case of cleanly rolling back the work item in progress, logging the failure and moving on to the next piece of work. This is why these tests are important in my release builds. I want to know that I will not proceed down the current code path if they fail. Being unable to proceed does not mean that the software cannot do useful work it just means that it can’t do this particular piece of useful work.

Also, I work with a lot of multi-threaded code so, for me, checks that are important enough for assertions in debug builds are probably more important in release builds. Some race conditions hardly ever show up in debug builds and yet could occur all the time in release builds. So, for me, these errors are important in release builds and if the system shuts down it’s better than it continuing. So yes, I guess I tend to work on Ferraris and others often work on Land Rovers where it’s more important for the program to give the impression of being in control even if it’s not entirely in control all the time. Obviously for Noel, with games, his requirements are more that the show must go on. Once his code is running on your console it doesn’t really help him a great deal to try and report the error to you as it’s unlikely that he can do anything about it. Your system probably fits somewhere between the two extremes.

So, in summary, if you can fix the design so that you don’t need an assertion then do that. If what you’re asserting must never happen in production code then either make sure this particular assert cannot be compiled out or use normal error handling code. If you feel you must use an assertion then consider using custom assertion code so that you can integrate assertion failures into your testing and so that the test can provide whatever it is that you require to do your job.