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.)



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:

  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
  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.

  • About that TryParse thing, something that's bothered me is that while there's Int32.TryParse (etc), there's no Enum.TryParse. Is there any reason for that? Enum.Parse is something I do on occasion, and it's, ehem, vexing that I have to do it in a try/catch...

  • The problem with the exogenous exceptions is that it's very hard to figure out the full list of ones you need to catch.  For example, try coming up with the full list of exceptions that could possibly be thrown when you open a file.  In addition to all the usual "file not found, directory not found, access denied, etc" messages you have to handle weirder cases, like the file being on a UNC share, the file being on an overlong junction, the file being pulled from a shadow copy, the file being served from a local directory that is actually a redirected folder, the file existing on a removable drive, the file living on a fake drive that is really a redirect to a URL, etc.

    Coming up with that full list of exceptions is really hard.  But if you miss one, your app crashes.  So, of course, you catch (Exception) and handle it with a generic "that didn't work" message.  But now you're committing the "sin" of "catch (Exception)".

  • The "do not catch fatal exceptions" need to be clarified a bit. I agree that you probably cannot do anything to recover from the error, but you can at least do some logging stuff. I guess you consider in this article that "catching an exception" and "Logging some stuff in a catch block" are two differents concepts.

  • Interesting classification. From a Java perspective, I'd say:

    Fatal exceptions are Errors (i.e. unchecked, and typically not caught)

    Boneheaded exceptions are typically RuntimeExceptions (i.e. unchecked)

    Exogenous exceptions are the reason for checked exceptions

    Vexing exceptions are the reason people complain about checked exceptions

    The relative frequencies of Exogenous vs Vexing exceptions in the language's libraries could determine whether checked exceptions are a good idea for that language or not.

  • This is an excellent top-level taxonomy for exceptions. My biggest general complaint about the .NET framework is that the framework's exception hierarchy is completely useless. The hierarchy really should have been broken into something like what you describe (though Vexing and Exogenous can't truly be split via the type system). Instead of ApplicationException and SystemException, we should have had something like System.FatalException or System.DangerousException (parent class of StackOverflow, ExecutionEngine, AccessViolation, etc., the ones that should basically never be caught), System.BoneheadException or System.CodeErrorException (parent class of Argument, NullReference, or IndexOutOfBounds, etc., the ones that should immediately trigger a bug report), and System.RuntimeException (parent class of most other exceptions, the ones that might indicate a runtime failure but might also indicate a bug or a design flaw).

  • XmlSerialization => FormatExceptions left right and center.

    Defininitely in the Vexing section if you're trying to debug a specific formatting problem in it since lots of perfectly valid stuff is dealt with by catching the FormatExceptions.

  • @C

    While it doesn't catch all of them, you can catch IOException to get a lot of them.

    I've always thought that while having the explicitness of Java's checked and unchecked exceptions (having to explicitly say what it throws) is bad, it would have been nice to have a distinction between fatal and non-fatal exceptions (a different base class). That way you can catch non-fatal exceptions for things like logging while letting fatal exceptions through.

  • Sorry Doug I disagree. they type hierachy is a very blunt tool which shouldn't be used for this purpose.

    If you are catching an exception  you either want ***really*** specific. So your response can be well defined (FileNotFound, Autorization, specific IO exceptions mainly[1]).

    If you're catching anything else it is solely note the occurrence (optionally increasing the amount of information available) and rethrow or handle a domain transition (between app domains or to create your own specific exit code for a process) as such Catching Exception is fine

    Anything else is almost certainly a BadIdea

    [1] something the could definitely be improved, If I open a stream I'd like to know the difference between a file being locked verses a file being read only verses not being there etc...

  • 'Writing good error handling code is hard in any language'

    Absolutely! thats why I think more effort should have been put into the exception strategy for .Net.

    Yes the idea of 'checked exceptions' in java seems a good idea in theory but in practice it can get out of hand and I can understand why this wasn't implemented in .Net. I can also see there were ideas that all user (dev = me) exceptions should have been derived from system.application and therefore easy to handle but that idea broke down very quickly. I don't understand why the exceptions aren't grouped\segregated along the lines of namespaces - it would be nice to be able to catch just one exception type for all user related file exceptions - invalid path, invalid filename, permissions etc.

    I would love the ability to do this in a 'contract first approach' - be able to define on an interface the exception types that can be generated by the implementation, then at compile time this constraint could be checked and verified.

    Anyway great post cos I'm in the middle of trying to sort kinda thing out :)

  • 2 Dean Harding

    You can use Enum.IsDefined for checking and then safely call Parse.

  • Eric wrote this:

    "Just let your "finally" blocks run and hope for the best."

    Keep in mind that there are times when a finally block will *not* run, and they're almost always during Fatal exceptions.

    What can we do about it? Don't put "hast to be run" code in your finally block -- only use finally for resource de-allocation.

  • In the try/catch case, the catch covers all the processing that occurs within the using block. To avoid this you have to manually code a try/catch Dispose, as using cannot be attached to an already assigned variable. Both of these options smell bad. Is there a trick that can get you the best of both - limited scope on the FileNotFoundException, and the syntactic support of the using block?

  • > as using cannot be attached to an already assigned variable

    Why do you say that?

  • Perhaps I'm mistaken? I just tested this in VS2008 and VS2005 and it works, so my memory is foggy. More likely I hit the 'Use of unassigned variable' error case if you don't have the throw or return in the sequence below.

    The sequence of Declare mydisposable / try { assign mydisposable } catch { throw or return } / using (mydisposable) { } is a little clunky, but not half as bad as without being able to use using that way. Thanks!

  • Is there another category of exceptions, which perhaps I would term "internal exceptions"? (Perhaps this is really just a subcategory of exogenous exceptions?)

    What I mean by "internal exceptions" is exceptions you throw and catch internally to a single component because doing so makes your program structurally more readable and maintainable, but that you do not (necessarily) intend to let fly out of your component to an external consumer.

    For example, suppose you have a component which parses some sort of fairly complex data file. Structurally, let's say it's convenient to write your parser in a recursive fashion, burrowing top-down through the file as it encounters elements to parse. However, errors of various kinds could be detected at any one of many levels of this parsing process, including all the way down at a leaf node. Let's say it's okay to stop trying to parse the rest of the file when you encounter the first error. How do you report these errors back up to the caller?

    You could use the traditional error code reporting method, bubbling back up a return code at each level of parsing, but that adds a lot of boilerplate error passing code to every level. It's also not terribly easy to elegantly attach additional information to the error. So instead, I think a perfectly reasonable pattern is to throw exceptions where errors occur, and then catch them all at the top-level method of the parser. You can then decide how to report this to the caller (maybe letting them handle the exception instead is okay, or maybe you want to use another method).

    It's an exogenous exception in that the root cause is something not under your control (the input file, which came from elsewhere), but you threw the exception yourself because it was a convenient way to structure your code.

Page 1 of 3 (40 items) 123