Vexing exceptions

Vexing exceptions

Rate This
  • Comments 40

Writing good error handling code is hard in any language, whether you have exception handling or not. When I'm thinking about what exception handling I need to implement in a given program, I first classify every exception I might catch into one of four buckets which I label fatal, boneheaded, vexing and exogenous.

Fatal exceptions are not your fault, you cannot prevent them, and you cannot sensibly clean up from them. They almost always happen because the process is deeply diseased and is about to be put out of its misery. Out of memory, thread aborted, and so on. There is absolutely no point in catching these because nothing your puny user code can do will fix the problem. Just let your "finally" blocks run and hope for the best. (Or, if you're really worried, fail fast and do not let the finally blocks run; at this point, they might just make things worse. But that's a topic for another day.)

Boneheaded exceptions are your own darn fault, you could have prevented them and therefore they are bugs in your code. You should not catch them; doing so is hiding a bug in your code. Rather, you should write your code so that the exception cannot possibly happen in the first place, and therefore does not need to be caught. That argument is null, that typecast is bad, that index is out of range, you're trying to divide by zero – these are all problems that you could have prevented very easily in the first place, so prevent the mess in the first place rather than trying to clean it up.

Vexing exceptions are the result of unfortunate design decisions. Vexing exceptions are thrown in a completely non-exceptional circumstance, and therefore must be caught and handled all the time.

The classic example of a vexing exception is Int32.Parse, which throws if you give it a string that cannot be parsed as an integer. But the 99% use case for this method is transforming strings input by the user, which could be any old thing, and therefore it is in no way exceptional for the parse to fail. Worse, there is no way for the caller to determine ahead of time whether their argument is bad without implementing the entire method themselves, in which case they wouldn't need to be calling it in the first place.

This unfortunate design decision was so vexing that of course the frameworks team implemented TryParse shortly thereafter which does the right thing.

You have to catch vexing exceptions, but doing so is vexing.

Try to never write a library yourself that throws a vexing exception.

And finally, exogenous exceptions appear to be somewhat like vexing exceptions except that they are not the result of unfortunate design choices. Rather, they are the result of untidy external realities impinging upon your beautiful, crisp program logic. Consider this pseudo-C# code, for example:

try
{
  using ( File f = OpenFile(filename, ForReading) )
  {
    // Blah blah blah
  }
}
catch (FileNotFoundException)
{
  // Handle filename not found
}

Can you eliminate the try-catch? 

if (!FileExists(filename))
  // Handle filename not found
else
  using ( File f = ...

This isn't the same program. There is now a "race condition". Some other process could have deleted, locked, moved or changed the permissions of the file between the FileExists and the OpenFile.

Can we be more sophisticated? What if we lock the file? That doesn't help. The media might have been removed from the drive, the network might have gone down…

You’ve got to catch an exogenous exception because it always could happen no matter how hard you try to avoid it; it’s an exogenous condition outside of your control.

So, to sum up:

• Don’t catch fatal exceptions; nothing you can do about them anyway, and trying to generally makes it worse.
• Fix your code so that it never triggers a boneheaded exception – an "index out of range" exception should never happen in production code.
• Avoid vexing exceptions whenever possible by calling the “Try” versions of those vexing methods that throw in non-exceptional circumstances. If you cannot avoid calling a vexing method, catch its vexing exceptions.
• Always handle exceptions that indicate unexpected exogenous conditions; generally it is not worthwhile or practical to anticipate every possible failure. Just try the operation and be prepared to handle the exception.

  • "...but you threw the exception yourself because it was a convenient way to structure your code."

    Expection handling!

  • It's pretty rare that I do straight-up trackbacks into other people's blogs, but the latest post by Eric Lippert really deserves it. He discusses the four different classes of exceptions very eloquently. While he's talking about .NET, the same truths..

  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • I attended a session by Jason Clark, who claimed to have worked on defining the exception strategy for the BCL (in v1.0?). He said the rule was:

    * The method name MUST be (contain?) a verb describing the effect of the function.

    * If that effect does not completely occur, the method MUST throw an exception.

    Thus double.Parse(string) throws an exception if it does not parse the string.

    Also:

    * If there are common causes of failure, ways SHOULD be provided to check if it will fail before calling the function (as far as possible).

    E.g. : Enum.IsDefined, File.Exists

    This isn't always provided, and can't always be bullet-proof (e.g. File.Exists, as described earlier) but helps avoid many common exceptions.

    IF there is a very common failure scenario where using exception handling causes a (measurable) performance degradation, then there's another set of rules:

    * Add another function that handles that error condition and JUST that error condition by returning e.g. false.

    * The new function MUST have a name that reflects the fact that it might not succeed.

    Thus: bool double.TryParse(string) which will, of course, throw exceptions if it hits any failure other than a badly formatted string (e.g. OutOfMemoryException).

    He also said that the reason that CLR exceptions are slow is that they are built on top of Win32 SEH. The CLR team apparently told him there was no reason they HAD to be built on top of SEH, it was just easier. They apparently said that if the speed of exceptions ever got into (e.g) the top 10 performance issues seen in real applications, they would probably re-implement them to be faster. The implication, of course, is that they're along way outside the top 10 right now! :-)

  • hmm, interesting idea - how would we implement this?

    public class LippertException: Exception, ISerializable;

    public class FatalLippertException: LippertException;

    public class BoneheadedLippertException: LippertException;

    public class VexingLippertException: LippertException; /* or fom bonehead? */

    public class ExogenousLippertException: VexingLippertException;

    maybe? ;-)

  • So this article sparked a bit of a controversy where I work.

    We all agree with your logic of Vexing Exceptions but we do have one area that none of us can agree on.

    When a user enters in the username and password does the login Manager throw and exception or does it offer a TryLogin?

    Some of us believe it should be an Exception because you expect the user to be logged in and it is exceptional if the users credentials are incorrect.

    While the rest of us think that you should always expect that the users input could be wrong making it not exceptional if they put in the wrong credentials.

    So really the argument comes down to which persons point of view for an exceptional case is programmed for. Is it the user or is it the developer? Users expect to be logged in thus the Exception handling on login, however, the Developer expects the user to be incorrect thus no exception.

  • I don't see what's vexing with double.Parse other than that exception handling is so slow. Part of the value of exceptions is that you can't accidentally forget to check the return code and muddle on thinking that - in this case - the string was correctly parsed. So *is* this just about performance, or are there deeper reasons to dislike exceptions? What exactly is "vexing" about a method throwing an exception when it is unable to do what you asked of it? Many people seem to agree that "exceptions should only be thrown in exceptional circumstances", but everyone seems to understand something slightly different by "exceptional circumstances".

  • @Michael

    Interesting question: to add my $.02, I don't think the user's should care how this is implemented.  All they need to know is that their credentials were incorrect, not how the login code treated the failure.  Moreover, it's not necessarily exceptional to have the wrong credentials, particularly if you've changed your password recently, like I have ;)

    I wouldn't say that the developer "expects" the user to be wrong, either.  I think the developer doesn't know what to expect.  Either case is as likely, so I would probably indicate the failure by a return value.

  • I don’t like the concept of exogenous exceptions too much. There is nothing magical in exceptions in this situation – the OpenFile method (note that you did not use a constructor!) might as well return null (etc.) in case of an error instead of throwing an exception. The fact that you need to have atomic check/open has nothing to do with exceptions. So you could have categorized this as a vexing exception, too. The difference is just a bit of taste and syntactic sugar.

  • These are taught at university Java courses, in the format Inigo pointed out.

  • Good thoughts.  I liked your definition of exceptions and your categories.  Your pointing out the race condition was superb.

    One thing I disagree with is not trying to catch them.  I try to catch everything so I may log the errors.   Otherwise, the error may thrown and the program die, but the admin won't know what caused it.  Could bad logic have allowed the program to terminate willingly, even though it wasn't ready to end or did someone send a kill signal to it?

    And even though they can be a pain, .NET stack traces have the ability to provide some critcal information, so if you do nothing more than catch and log a general exception, you have set your self up to proactively understand what caused your program to cease.

    Secondly, you stated to not catch bonehead exceptions.  In my opinion, you never want the exception to be thrown up the stack until it displays the trace to the screen or spit out an unhandled exception error for the user to see.  Log it, email it, anything, but do something with it, then rethrow it or return a "polished" error message.

  • @Jarrod

    I think you took Eric too literally. When he says "don't catch" these exceptions, he means don't catch them at the point where they occur in an attempt to soldier on despite the problem. Of course it is good practice to use a global error handler to log any "unhandled" exceptions that reach the start of the call stack, and display an appropriate error message to the user.

  • Sure. Use mechanisms (such as Watson) to backstop exceptions and report them to the user, or, even better, to the development team.

  • The problem is trickier than it seems. If the blah blah blah code itself throws a FileNotFoundException, do you still want to catch it? Probably not, since it broadens the catch beyond its intent. You're quite prepared for the file at hand to be missing or unreadable, but do you really want the same catch block to trigger when a configuration file is missing deep inside some library code? More likely than not, you want the latter exception to go straight to the debugger. Delegates or lambdas provide an interesting generalised solution for this:

    public static bool Exogenous<T, E>(Func<T> get, Action<T> use) where E : Exception

    {

       T value;

       try {

           value = get();

       } catch (E) {

           return false;

       }

       use(value);

       return true;

    }

    Usage is quite simple, though some may need time to acclimatise:

    if (!Exogenous<File, FileNotFoundException>(

            () => OpenFile(filename, ForReading),

            file => {

                // blah blah blah

            }))

    {

       // Handle filename not found

    }

  • Interesting article.  One of the first things I do when I'm given unfamiliar code is search for "catch" to see what kind of developer(s) I'm dealing with.

    Nothing bothers me more than two patterns (which I see far too often):

    catch(Exception x)

    {

        throw Exception("Failed to achieve happieness");

    }

    and

    catch(Excetpion)

    {

       // no code at all here

    }

    return false;

    So while you're on the subject of catching things.   Don't obliterate important debugging information in your catch block.  Don't leave *any* catch block completely empty.  If there's nothing to do then use

    catch(Exception x)

    {

       // let the sap maintaining your code see what you're hiding.

       if(Debugger.IsAttached)

           Debug.WriteLine(x);

    }

Page 2 of 3 (40 items) 123