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 file 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
. Defect taxonomists call this situation a TOCTOU: Time Of Check is not Time Of Use.
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.
What I get from this is that exceptions are intended for “acts of god”, or more accurately, chaos from the “real world”, like broken or damaged discs, etc. (As you say, exogenous exceptions.) Anything else, and exceptions should either be ignored (fatal exceptions), or not used if at all possible (boneheaded/vexing). In other words, it sounds like your answer to “when should you throw an exception” might be pretty close to “never”.
The way i understood it, you *should* throw Boneheaded exceptions (e.g., precondition is not met) and Exogenous exceptions (e.g., underlying system reported failure via error code).
You should throw exceptions, but your code should be written in a way that doesn’t invoke that throw. The throw itself is entirely proper (e.g. throwing `ArgumentOutOfRangeException` when inputs are out of range), but they signify a problem in your code (except for the fatal/exogenous cases), and should result in you fixing the bug, *not catching the exception*. They help you maintain invariants at runtime, and find and quickly fix errors (e.g. instead of the nice `ArgumentOutOfRangeException`, you could have gotten something like `NullReferenceException` or `InvalidOperationException` or some such in seemingly unrelated parts of the code). If you access an index outside the bounds of an array, it’s a problem of *your* code, but it’s still entirely appropriate for the array to throw an exception when that happens. It’s just that instead of writing `try { … } catch (IndexOutOfRangeException) { … }`, you should do `if (i >= array.Length) …`.
Pingback: Wizards and warriors, part five | Fabulous adventures in coding
A question about fatal exceptions: Say I have a program which displays images. Potentially lots of big ones. For user experience I keep images in memory once they are loaded from disk. At some point I run out of memory and an OutOfMemoryException is triggered e.g. inside the Bitmap constructor.
Wouldn’t it be reasonable to catch that, dispose some old images (which will be loaded from disk again once they are needed), possibly tell the GC to C some G, and try again?
That seems like a valid example of catching fatal exceptions.
If the CLR and languages were redesigned today, would you choose something other than exceptions, a different mechanism, for boneheaded scenarios? Possibly something which cannot be caught like exceptions because doing so is “not the way to go”?
If you are loading so much image data that you literally encounter OutOfMemoryException, you are doing it wrong. There are only two scenarios that produce OutOfMemoryException:
* Either you have exhausted your virtual address space, which in today’s world should be close to impossible unless you’re intentionally opting into being a 32-bit process, or
* You’ve exhausted the entire system’s pageable memory, which is going to impinge in a very significant way on all other operations on the system.
You should not rely on OutOfMemoryException to be the limiting factor in either of these cases. If you do, your program is just plain rude.
I disagree mostly.
Yes, ideally my program wouldn’t take over the whole computer, but:
In my particular case I have a program which I use to view images. When I open a file I can also navigate through the adjacent files in the same directory by scrolling with the mouse wheel. So for me it is important that I have as big of a chunk of loaded images as possble such that I can scroll through them quickly. (This is useful for scrolling through timelapses, for example). My current strategy is (simplified for brevity): whenever a file is first selected, I load the file from disk and put it in a list of cached images. So when I come back to it later I don’t have to wait for it to be loaded from disk again.
At some point I might run out of memory when I try to load a file from disk. (As you have correctly guessed, I have a 32-bit process. More on that later.) If that happens I dispose some of the cached images and try again. I detect this situation by catching the OutOfMemoryException which is the best strategy for me right now. I could put some arbitrary maximum on the amount of resources I occupy so that I stop allocating RAM earlier, e.g. the number of simultaneously loaded images or the amount of RAM the current process uses. But that’s no good because of the following reasons:
1. If my limit is too low I don’t tap the full potential of how many images I could have loaded simultaneously. What even *is* a good limit / how do I determine it?
2. The OutOfMemoryException might happen earlier anyways if my limit is too high. Again, how do I even determine a good limit?
3. When I’m looking at images, that’s most likely the most important task at hand anyways so I want as many resources for that task as I can get ahold of, i.e. I want my limit to be as close to the real limit as possible, so why not simply use OutOfMemoryExceptions in the first place.
So in my case, the program isn’t rude at all 😉 In a different scenario I absolutely get your point, but in this case it’s not applicable.
So about the 32-bit process: By default, Visual Studio creates every project with TargetPlatform set to x86. (At least for me. Might be different for you and I think I can change that in the project templates.) And it is very finicky to change that in the Express editions of Visual Studio, which is why I haven’t changed it until now. Now that I have this project set to AnyCPU I do have more RAM available. In fact, when I tried to trigger an OutOfMemoryException I stopped after reaching ~20GB of “private bytes” (according to ProcessExplorer). So… Great, that works!
Pingback: Rubrics for Code? – willmurphyscode
Pingback: The Developer who Knew Too Much – willmurphyscode
I have strong doubts about the “99% use case” of the parse method. Rather, I’d expect the majority of the use cases to be configuration files, JSON or other text or XML based file formats (Office documents, CSV files, etc). Having to parse a string to a number which is truly entered by the user is rather rare.
Even when the string originates from the user, it is usually entered into a pre-configured smart textfield which validates the input and only passes valid input, so receiving an invalid string is a sign of a bug in some of the involved components or someone manipulating the HTTP stream, etc., in other words, clearly an exceptional case.
This doesn’t preclude the need for something like a TryParse method in a framework. That’s needed to implement the validating textfield or file format parser. But how often is an application doing this on its own?
This was really useful. I enjoyed that Vexing and Exogenous exceptions are the same, at least in terms of how to approach them, i.e. you have to handle them because you can’t avoid them as their triggering is outside your control: the only differentiator is the Vexing ones could have been avoided by their creator if they had coded better, so they need a separate category just to mark them as ones which make you angry. I love it.
Pingback: Common .NET Software Errors and How to Fix Them