Exception Handling Practices

Started by
17 comments, last by seanw 19 years, 6 months ago
My first encounter with exception handling was about six years ago when I was learning Java. Since then I've written and maintained exception handling related code in Java, C++, C# and a little bit of Python. Yet I still feel I don't understand everything about them and don't know how to properly use them. From what I've seen so far many people are in the same position as me (even though they don't necessarily realize it). So I decided to create this thread to clear up some confusion I have along with many others. I suppose there has been plenty of discussion on this earlier, but I did a search and couldn't find definite answers. Permormance. One of the main C and C++ design goals was to ensure that performance overhead associated with every feature is clearly defined. This doesn't seem to be the case with exception handling. Many sources I found provide conflicting information: some say you only encounter an overhead if you throw an exception (totally acceptable, IMO), others say overhead is encountered on each function call that might potentially throw an exception. It seems that the latter is the case and in my mind this raises concerns. How serious is this overhead? When exactly do I have to encounter it? Is it clearly defined? What can we do to minimize this overhead? Proper usage. I used to think Java style exceptions were great. Then I actually had to write a shipping product and I ended up writing things like throws Exception and catch {}. When I encountered C++ and C# exceptions I was very surprised about their approach: they didn't require me to catch all potential exceptions or declare them in my function as throwable. I then encountered Anders Hejlsberg's article The Trouble With Checked Exceptions. It's fairly short and isn't very detailed but it looks like Anders vocalized everything I felt subconsciously but was never consciously aware of. According to the article, for instance, a typical game will look like this:

while(...)
{
    if(HaveMessage)
        TranslateMessage();
    else
    {
        try
        {
            DoFrame();
        }
        catch()
        {
            // Handle Error Here
        }
    }
}

The catch block above would be one of the very few catch blocks in the entire application, even though 99% of the functionality will be implemented inside DoFrame(). I can't seem to wrap my mind around this. According to the article I'd log the exception and go on with the loop. But what if it's critical? My application will simply be stuck in an endless throw-catch loop. I could, of course, exit the application inside the catch, but what about exceptions that could easily be handled? Should I catch some exceptions inside DoFrame() and if they can't be handled rethrow them as CriticalException that the top-most catch block will recognize and exit while all other exceptions will simply be logged? How do you deal with all this? What do you think is the right way? Do you have any other resources handy that answer some of these questions?
Advertisement
Where you handle the error, you determine whether the exception is of the "game quitting" variety, or of the "keep trucking" variety.

Of course, if you're using an API (say, file system) that throws exceptions on error, but you're prepared to deal with errors (say, by substituting a yellow-and-purple cube for missing asset references), then you need to catch exceptions at the point of calling into that API.

Typically, if your resource ownership is traceable to locally scoped variables (auto pointers on the stack, etc), then exceptions can be surprisingly effective at writing "clean" code. But getting there is a tough slog through adapters, helpers, and lots of itty bitty support code that really should be standard, but never is.
enum Bool { True, False, FileNotFound };
The idea of exceptions is that you catch errors and handle them at a place where you can take appropriate action. For example, if I wrote a library to read in a file and the file was not found, having the library create a dialog box with "File not found!" is unlikely to be appropriate. Instead, I could throw an exception which the user (as in the programmer) can catch and handle in a way which reflects how the library is being used. So if a saved game file was not found, the programmer could catch this exception and make the player pick another filename. If it was an essential game file, say the current level map, there isn't a whole lot you can do to keep the game going, so you would probably just let your outer-most catch statement in your main function catch it and tell the user a fatal problem has occured.
Quote:Original post by hplus0603
Where you handle the error, you determine whether the exception is of the "game quitting" variety, or of the "keep trucking" variety.

If I do it according to Anders' article, I'll have no way of knowing. He suggests having a "main" catch block at the very bottom of the function call stack (in this case the main loop). First of all, there may be hundreds of exceptions thrown. I can't put hunderds of different catch statements there, I have to catch a few base ones. Second, suppose I catch a FileNotFound exception. How am I supposed to know at that point if it's "kill-the-app-now" exception or a "just-log-and-go-on" exception? The only way I can imagine is doing more specific exceptions closer to the top of the call stack and rethrow them as KillTheAppNow.
Quote:Original post by seanw
If it was an essential game file, say the current level map, there isn't a whole lot you can do to keep the game going, so you would probably just let your outer-most catch statement in your main function catch it and tell the user a fatal problem has occured.

So now the question becomes, does your outermost catch block assume any exception that leaked to it is fatal, or does it assume it's non-critical unless it's some specific type?
Quote:Original post by CoffeeMug
So now the question becomes, does your outermost catch block assume any exception that leaked to it is fatal, or does it assume it's non-critical unless it's some specific type?


Yes, most exceptions which make it to the very top of the call stack are likely to be fatal. At each point a function you call can throw an exception, you should be thinking about what would happen if an exception was thrown and where a sensible place would be to handle it. Fatal exceptions would be things like critical game files not be found, running out of memory, not being able to initialise your graphics engine etc. Things where you have literally no option but to just end your application.


Errors that aren't entirely fatal should be handled where appropriate. For example, say you have a DirectX renderer and a OpenGL renderer. When your game initialises and your DirectX renderer won't start up for some reason, your DirectX initialising function could throw an exception which your game initialising code could catch, where it would then try and setup the OpenGL renderer. If that didn't work, you could then throw a fatal exception which your main function would catch and give your user a decent error message to what's wrong. You wouldn't put the code to setup the OpenGL engine in your main function, for example, because that's not an appropriate place to put it as you'll be mixing your initialising code with code that isn't directly concerned with how your game initialises itself.
Quote:Original post by seanw
Errors that aren't entirely fatal should be handled where appropriate.

This makes sense although it contradicts Anders' article (and he's a Delphi and C# architect, I'd assume he knows what he's talking about). It seems this way is a lot less painful than logging all errors that slip to the outermost handler and specifically catching dangerous ones. At the very least they prove to be a lot less work.

What about the performance hit? Could someone clarify the performance issues associated with exceptions?
Quote:Original post by CoffeeMug
Quote:Original post by seanw
Errors that aren't entirely fatal should be handled where appropriate.

This makes sense although it contradicts Anders' article (and he's a Delphi and C# architect, I'd assume he knows what he's talking about).


What did I contradict? I'm probably just not being clear enough.



I skimmed the article and he's saying you should have a catch block in your event handler. If you're writing some kind of GUI or whatever, and user actions might cause some kind of exception (such as null pointers access or out of bounds bugs in your event handlers), it might be appropriate to catch them in the event loop, pop-up an error box and continue. It depends on the application though. It might be the case that any exception being thrown in your application is fatal because it indicates the whole program has failed so you should just exit. Either way, you should really have a catch statement around the whole program called from main (what if your event handler throws an exception?) just to catch the catestrophic errors that kill your program so you can give a diagnostic message.



As a general rule, you should catch and deal with an exception (and possibly rethrow) at a point in the program where you can make a sensible action to resolve it. So, for the file library example, catching the FileNotFound exception in the library is not sensible because you don't know what kind of application your library will be used for and what would be appropriate (e.g. a pop-up dialog for a user interface or a console output for a server?). Only the library user can decide on this.

Quote:
It seems this way is a lot less painful than logging all errors that slip to the outermost handler and specifically catching dangerous ones. At the very least they prove to be a lot less work.


Hmm, I don't really understand what you mean here. What way is a lot less hassle? What do you mean by logging all the errors?

Quote:
What about the performance hit? Could someone clarify the performance issues associated with exceptions?


Exceptions generally only incur a speed-hit when they are thrown but exceptions should only be thrown in exceptional circumstances anyway so this should never be a bottle-neck. You have to consider what you are comparing their speed to as well. You're really comparing them to checking every error return code of every function that could fail and manually returning up the call stack. Even if that way was faster, it would be messy and horrible for maintenence. Do some benchmarks yourself if you want a better answer, but they aren't going to kill your performance. I recall Tim Sweeny uses them a lot of the Unreal code if that makes you feel better about them.
Quote:What about the performance hit? Could someone clarify the performance issues associated with exceptions?

On Windows, C++ and .NET exceptions are implemented atop Win32 structured exception handling. What does that mean? Every time you throw an exception, you get to make a happy-trip back to kernel mode code before beginning to unwind the stack! Not fun!

Everyone's definition of what exceptional is will vary. I'm undecided on what should happen if you pass invalid arguments into a function, say. In fourth-gen languages you get lamblasted with an ArgumentException or ArgumentOutOfRangeException, but this seems frivolous -- are we meant to catch these exceptions? If we are not, then what is the use of having them there? To reinforce the notion, that, Everything Is An Object And That Is Good? (Except For Value Types!)

Exceptions are pretty damn tricky. If I decide to use them somewhere I always write the catch clause first around the area that may throw so I can test to see if some degree of transaction-ness/atomicity is needed. If it is then I tend to shy away from exceptions (I haven't read Sutter's Exceptional C++ series yet -- not exactly the most fun book to buy, you know.).
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
Quote:Original post by seanw
What did I contradict?

He's saying one should just pop up a message box in the outer most catch block and go on, you're saying one should quit the application in the outer most catch block.
Quote:Original post by seanw
If you're writing some kind of GUI or whatever, and user actions might cause some kind of exception (such as null pointers access or out of bounds bugs in your event handlers), it might be appropriate to catch them in the event loop, pop-up an error box and continue.

I absolutely hate this type of crap. Every time I see an application (usually Java) pop up a NullPointerException dialog and then go on as if nothing happened when I click "OK", man, I just get the word "UNSTABLE" flash in my mind in big red letters. I'd rather have the application crash on me than pop up a box and go on.
Quote:Original post by seanw
It depends on the application though.

I guess, although I can't imagine an application in which it's appropriate to suddenly show up a NullPointerException to a user and then go on as if nothing happened.
Quote:Original post by seanw
Hmm, I don't really understand what you mean here. What way is a lot less hassle? What do you mean by logging all the errors?

My original idea was to have a try-catch block in the message loop that simply logs all errors unless a specific type of an exception is thrown (say, FatalException) in which case it would quit the application. That would require me to go through the code and catch every single critical exception and rethrow it so the outermost block would recognize it as a critical one. I now see that this isn't the correct course of action.
Quote:Original post by seanwExceptions generally only incur a speed-hit when they are thrown but exceptions should only be thrown in exceptional circumstances anyway so this should never be a bottle-neck.[...]I recall Tim Sweeny uses them a lot of the Unreal code if that makes you feel better about them.

I am not worried about exceptions slowing down my uber-1337 MMORPG 3D engine [smile] I'd just like to know exactly what type of overhead they cause (not in cycles or milliseconds, but conceptually). I'm not worried about slow throws, this is why they're exceptions, exceptions to the normal codepath. I'm worried about (potential?) overhead on entry and exit of every function that may throw an exception. So far nobody was able to tell me if this overhead really exists.

This topic is closed to new replies.

Advertisement