Wednesday, May 2, 2007

Checks, exceptions and assertions

When programming in most modern languages, there are basically three different ways to deal with errors or abnormal situations: checking, exceptions and assertions. However, some people seem to misunderstand when to use which.

A check is the oldest trick in the book. It's simply an if (or equivalent) statement that checks whether a certain error condition holds. An exception can be raised (thrown) at the point where the error occurs, and then handled (caught) by any function lower down the call stack. An assertion is also a sort of check, but usually in the form of a library call or a language construct. A failed assertion usually results in terminating the program. Assertions can often disabled by a compiler parameter.

Now here's the flowchart that'll tell you which way of error handling to use:

  1. Do you think this error will ever occur?
    1. No » Use an assertion.
      Assertions reflect a claim made by the programmer: “this will never happen”. If it does happen, it must signify a bug. If you use assertions for anything else than validating things that you think must be true, you're using them incorrectly. Also, it's perfectly okay for a program to abort (with a meaningful message, mind you) when it encounters a bug during testing.
    2. Yes »
      Will this error occur in normal circumstances?
      1. No » Use an exception.
        Exceptions are made for, well, exceptional circumstances. They're meant for situations that you overlooked, or chose to overlook because they seemed (rightly or wrongly) rare enough that a check wasn't worth the effort. The great thing about exceptions is that they can be handled at a much higher level than the place where the error occured, a power that simple checks don't have. You can use this to catch the rarer errors at a high level, not having to scatter error checks all over the place.
      2. Yes » Use a check.
        Verify whether a file given on the command line exists. Do make sure that a person's last name does not contain any quotes. These are things that are actually expected to fail occur once in a while, even under normal circumstances. You can nearly always handle them locally, so there is no need to throw an expensive (performance-wise) exception around.

It's quite simple, really.


Mark IJbema said...

Another reason to use exceptions instead of checks is that for some functions you can't do anything useful at that point, because you have no context. For instance, let's say you are building a library function which divides an integer by another integer. If the second integer is zero there's no way you can ever resume the normal control flow, since there is no correct answer to return anymore, however, it need not be exceptional to get a zero for the second argument, even though it's clearly an incorrect value.

Anonymous said...

That of course is a runtime exception that you wouldn't really be explicitly checking for or throwing. A little different from the type of exception decision thought process that is defined above imho.

Damian Yerrick said...

Even if you check for a file's existence, you may still need an exception handler to avoid race conditions, or situations that arise between the time of check and the time of use (TOCTTOU). Because you need an exception handler anyway, applying Don't Repeat Yourself (DRY) would result in dropping the check in favor of Easier to Ask Forgiveness than Permission (EAFP).