Do’s and Dont’s for exception handling

… Or what every developer should know about the implementation of exception handling:

Modern Exception Handling (EH) in C++, JAVA, Ruby, Modular-3, C# and other modern programming languages is a great tool for handling errors but unfortunately it is sometimes abused by software developers that do not quite get what exceptions are really for or are just ignorant of possible implementations.

Common abuses of EH includes using exceptions as an alternative flow control mechanism (think sophisticated “goto’s” and you got the basic idea of this antipattern”)……. Don’t do that. It will only make the code harder to read. It will also make your code slower to execute since throwing exceptions are generally very expensive operations.

Another less apparent misuse of EH is usage of try-catch(-finally), or similar constructions your language may offer, inside the control flow of hotspots (such as inside time critical loops). Don’t do that, as a the try-catch-finally construction may have overhead even when you won’t expect it.

So why are throwing exceptions expensive and why may the try-catch-finally constructions (or similar) have overhead ? Well, it all depends on the language, the implementation of your VM or compiler (and sometimes on whether you use native code or not if your language allows it). Depending on your environment, just raising one exception can be from 10-100.000 times as slow as alternatively returning a simple return code from the method. And even if you don’t raise any exception, just having a try-catch-finally in your control flow can also be moderately expensive (but usually only enough to be a problem inside hotspots).

Specifically, the case of overhead of try-catch-finally constructions when no exceptions occur is difficult to get rid of by compiler & virtual-machine implementers. Few implementations on selected chip architecture got it right and have 100% overhead-free implementations but many impose a overhead just for placing try/catch/finally constructions in your control flow. Basically this is because something like a “linked list” has to be maintained internally by the compiler or VM each time the control flow enters or exits a try-catch-finally.

For much more details about various possible implementations of exception handling and the impact on performance refer to this old thesis of mine here.

In conclusion, the morale of the story is:
* Do use exception handling for error handling only (not for control flow).
* Don’t use try-catch-finally constructs inside hot-spots (i.e. loops and such) if it can be avoided. Do the try-catch at a higher level that is called less often.
* If your particular java, c++, ruby, clr … implementation of exception handling on one chip architecture yields excellent performance even when you break the above rules you are just plain lucky. Change the version, vendor or chip architecture and you luck may desert you. Therefore don’t do it :-)

8 Responses to “Do’s and Dont’s for exception handling”

  1. Tom Palmer Says:

    Don’t forget that “finally” is a frequent requirement (even if “catch” is usually a bad idea).

  2. Thor Åge Eldby Says:

    The most common problem I run into are those who catch checked exceptions and don’t do not do anything with them. Those who just put the exception to the log are not any better.

    I can see three reasons for doing this: Ignorance (this is what the IDE created automatically) and ignorance (we can’t let an error interrupt the flow of the program and blow up in the customers face) and ignorance (we’ll pick the errors up from the log later).

  3. mortench Says:

    Tom Palmer: Usally the cleanup actions that require finally constructions can be moved. Just do the finally outside the performance hotspot and you are ok.

    Thor Åge Eldby: I agree. I have also seen quite a few mistakes in code where exceptions are catched and then ignored. I have even seen senior developers doing this in difficult places like event-handlers where one have to go the extra mile to actually handle the error (one way to do this is to create have the main thread manage errors and transfer exceptions to that thread in the event handlers ; but that is long story so I will reserve that for another blog posting :-))

  4. Ricky Clarkson Says:

    Using exceptions only for error handling, i.e., for ‘exceptional’ cases, means that you are treating these cases as abnormal, when in fact they are normal.

    It is normal for a stream you are trying to read from to be closed, this is not an error, it is just a situation.

    There are two ways you can go, and I’ll be using Java syntax, so there.

    1. Use exceptions for flow control.

    Acknowledge that exceptional conditions are actually normal conditions, but you like the fact that exceptions give you more than one return path from a method. I tried this out:

    interface ViewIterator<T> { T next() throws NoSuchValueException; }

    That is, I didn’t have a hasNext in my iterator, I just caught the exceptions.

    This worked, some code was more verbose, some less, but there was a performance hit from populating the stack trace (I used hprof, part of the JVM: http://java.sun.com/developer/technicalArticles/Programming/HPROF.html ). It turned out later that I could have turned off the stack traces ( -XX:+StackTraceInThrowable or -XX:-StackTraceInThrowable, not really sure which), but that would have negatively impacted the rest of my code.

    So I put my hasNext back in, and my code sped back up again. Fun experiment.

    2. Don’t use exceptions for anything. The only way this can work is to pass a callback, or a closure, unless you’re happy with returning status codes from methods, such as null.

    interface ViewIterator<T> { void next(T item,Callback<T> callback); }

    This should work for all cases, but I haven’t tried it out yet on my codebase.

    An alternative to this is to pass two callbacks:

    interface ViewIterator<T> { void next(T item,TypedCallback<T> callback,Callback ifNone); }

    The idea there is that callback.call(t) is called when there is a next item, and ifNone.call() is called when there isn’t.

    Conclusion:

    I think that most of us will find a comfortable home between these two ‘extremes’, or just waddle along without being very clear. It’s hard to be consistent anywhere between these two points.

    An Iterator not having a next is arguably less unusual than an InputStream being closed by a remote party, but should we let frequency of a condition define whether it is to be made into an exception or not? I think not, as that’s an easy way to be inconsistent.

  5. mortench Says:

    I am not sure if we understand each other but it sounds like we are in disagreement… Not having the time now for an entire essay, I will just briefly point out:

    You mention examples with trying to read from a file when it is closed (by you) and calling next() on a interator that has no more elements. Both examples should not occur if your code is written correctly (*). Hence, it is acceptable in both cases to throw exceptions for this error condition. In other words, both examples are errors and not “situations”… I repeat: Do use exceptions for error handling but don’t use exceptions for anything else.

    (*) This assumes synchronous execution flows. The case of a connection being closed asynchronously by another party during your own processing is less clear. What to do here depends on the exact situation. Will have to write an entire essay to adress this point so I won’t for now.

  6. Ricky Clarkson Says:

    > You mention examples with trying to read from a file when it is closed (by you)

    No, I said reading from an InputStream. The InputStream may be closed by an aborted connection or any number of cases, e.g., a lost handle to an NFS/SSHFS filesystem, so that is not invalid code.

    In my example Iterator that only had next(), you would *have* to call next to find out whether there was an item, so it would not be invalid code.

    This is similar to calling ResultSet.next to find out whether there is a next item, and simultaneously moving to it.

    If you accept that some things are just errors, then you are relegating their importance. In fact, these situations are a big cause of bugs. When the most expected paths through code work, then testing is done. I’ve seen it happen, and done it myself.

    About code being written correctly.. Like it or not, incorrect code is also normal.

    If you provide an API that requires things to be done in a certain order, then a normal case is that the user of the API will fail to do them in that certain order. Hopefully this is not a frequent case, but it is normal, in that it is possible.

    I’m not too worried about that yet in my code, but I have thought a little about it. One common approach, say, if someone passes null to you, is to let or force a NullPointerException or IllegalArgumentException to happen.

    However, that isn’t flagged up by the compiler, and so the API user might only think about it at runtime, which is not as early as possible.

    Again, there are two ways to go, the same two ways:

    1. Make the method throw a checked exception.

    This is syntactically annoying for the client, especially the one who knows they aren’t giving you null.

    try
    {
    yourMethod();
    }
    catch (CheckedIllegalArgumentException exception)
    {
    System.out.println(”Oops, I did it again”);
    }

    2. Make the method (or the object that it is called on) know about an exception handler:

    yourMethod(Handler nullPointer);

    Then the syntax can be as minimal as:

    yourMethod(Handlers.DO_NOTHING_ON_NPE);

    This way does not get in the way of the readability of the code, but does make the API user consider what to do in the case of their own mistake in using your API.

  7. Jason Watkins Says:

    “Another less apparent misuse of EH is usage of try-catch(-finally), or similar constructions your language may offer, inside the control flow of hotspots (such as inside time critical loops). Don’t do that, as a the try-catch-finally construction may have overhead even when you won’t expect it.”

    Could you explain more? I was under the impression that the majority of modern compilers simply attatch exception catch information to the stack. A simple, minimal impact on performance. What sort of circumstances have enough unexpected overhead to worry about?

  8. mortench Says:

    Jason Watkins: I hope the following extract help - It is from chapter 6 of an old thesis of mine (linked to in the posting) and mostly C++ oriented but the theory remains valid for C++ and other modern OO languages:

    “When an exception is raised the EH mechanism must transfer control from the signaller to the handler of the exception… When propagating an exception all previously constructed objects, that are brought out of scope, must be destroyed (note this last point is for C++ only, not for Java, C# etc).

    The handling of exceptions must be done at runtime, since it is generally not possible to predict in advance which handler to transfer control to, identify which exception has been raised, where to perform object cleanup during the propagation of an exception and how much memory to pre-allocate for exception storage. Usually the EH runtime mechanism is compiler-specific and a part of compiler’s runtime library. However in some cases support for EH is provided through the operating system.

    The transfer of control and object cleanup areas in the EH mechanism are closely connected. They are both very important for how portable and efficient the mechanism is. It is the implementation of these areas that decides the overruling part of the cost of the EH mechanism. In comparison the exception identification and storage management areas are of isolated nature and only of limited importance with respect to how portable and efficient the EH mechanism is.

    Basically, for methods, two different and well-known approaches to EH exist that I describe below as:
    . The dynamic registration approach.
    · The static table approach.

    The dynamic registration approach:
    When the dynamic registration approach is used the fundamental component of the EH mechanism is a dynamic exception information structure that is continuously updated while the program is running in its normal state. In the dynamic structure, which is normally a stack of information-markers, it is registered when a scope which includes exception handler(s) is entered or left, whenever objects are constructed/destroyed, and when a function that includes a raises-set is entered or left.

    When an exception is raised the EH mechanism backtracks over the dynamic structure using its previously registered information to locate the correct exception handler, all the while appropriately destroying objects and checking the legality of the propagation of the specific exception.

    The dynamic registration approach is attractive in the aspect that it has little need for close co-operation between the EH mechanism and the target system and therefore is very portable. Significantly, EH using the dynamic
    registration approach can be implemented in a high-level language9 as demonstrated by the C implementations discussed by [Cameron92] and [Koenig90].

    The drawback for the dynamic registration approach is that it, due to the continuing updates of the exception information structure, introduces an ever-present overhead in the normal program flow! An overhead which is
    much aggravated by the C++ object model’s requirement that objects must be cleaned up in the event of an exception.

    On the other hand, but less important, the dynamic registration approach offers optimal performance for the actual handling of an exception.-Optimal because only the information registered in the dynamic exception structure needs to be examined which, in opposition to the static table approach, makes this approach independent of the distance between the raiser and the exception handler.

    Where EH is a part of the operating system (OS) a dynamic registration approach is always used. OS calls are used to continuously inform the EH mechanism about changing exception handlers and cleanup actions. Note that in comparison with a compiler specific implementation, the use of the OS will impose an additional overhead due to the operating system calls.

    The static table approach:
    When an ideal static table approach is used the EH mechanism relies completely on static table(s) build by the compiler. The tables describe guarded regions of code and associated exception handler(s), raises-sets, and destructible object(s) and where they are constructed and destroyed.

    When an exception is raised the EH mechanism uses the program counter (PC) to search the table for information about the region of code in which the exception has been signaled, including information about exception handlers, objects to be destroyed and raises-sets. When an exception needs to be propagated the mechanism “hijacks” the return address from the stack, unwinds the activation record from the stack and then restarts the process by using the hijacked address as a new PC value.

    Characteristically the ideal static table approach imposes absolutely no run-time overhead in the normal program flow - all the work is done when an exception is actually raised!

    The major shortcoming of the static table approach is that it needs close co-operation between the EH mechanism and the target system thus requiring a native, non-portable implementation. Also the static table approach requires of the target system’s architecture the possibility to capture the return address and manipulate the stack in the above described manner during unwinding.

    Furthermore if exceptions are to be used in mixed-language programs using a method based on an ideal static table approach, a consistent well-defined call-frame structure is required of the processor architecture to support the stack-unwinding.

    Raising an exception with the static table approach is relatively more expensive then with the dynamic registration approach, since the entire work is done when an exception is raised. Specifically, the cost of raising
    an exception is proportional to the number of subroutine calls between the exception handler and the raiser of the exception. However the increased cost of raising an exception is not a serious disadvantage if EH is used only
    to handle errors, which can be presumed to happen infrequently.

    Note that the static table approach, although still technically possible, may not be a true option for a compiler for an operating system (OS) that integrates support for EH using a dynamic registration approach. An OS
    supported static table approach to EH, although complicated, should in theory be possible.”

Leave a Reply


Bad Behavior has blocked 145 access attempts in the last 7 days.