Everything wrong with exceptions

In my previous article I looked at a basic reason why exceptions are necessary. In retrospect it was more of a look at why simple error codes are insufficient; it isn’t clear that C++/Java style exceptions are the correct solution either. I do suspect some kind of exception-like mechanism will be required. With that in mind we should look at the problems exceptions have. That is, despite being necessary, why is it that they aren’t universally accepted.

Here I’ll present a list the major criticisms of exceptions. I won’t go too deeply in to each topic but will try to give an overview of the problem. There is obviously an endless list of complaints about exceptions, but mostly they can be grouped into the items here.

Performance / Efficiency

Performance is probably the most controversial topic about exceptions. While the concern is legitimate at times, the issue is complicated by a lot of misconceptions and bad information. The first issue is with the memory requirements of exceptions. Whether it is a simple descriptive string, or a full stack trace, there is data overhead to the exception. This isn’t a requirement of exceptions however. Consider that in C++ you can throw simple integer types if you really wanted. Alas, many languages don’t offer lean exceptions and you’re stuck with what you get.

Execution time of exceptions wildly varies depending on language and compiler. In a fair comparison you always have to consider two different timings: the non-exception code flow and the exception code flow. The former refers to the time it takes the code to execute when an exception doesn’t happen, and thus the latter is the cost of propagating an exception itself. In a return code model these times are combined together as you must always deal with the return codes. The cost is the same whether there is an error or not.

It should be noted here that exceptions can be implemented with zero cost in the non-error case — and some C++ compilers do this. The cost comes at throw time and involves table lookups. Should you be writing a hard real-time program, the added memory of these lookup tables might require investigation.

Library Boundaries

Unlike simple integer error codes there is no agreed definition of what an exception actually is. Each language has a slightly different take on it. Exceptions are not compatible between languages, and often not even between different compilers of the same language. This means that on the boundaries to your code, like library functions, exceptions are often forbidden. You have to revert to simple error codes at this point.

Perhaps this wouldn’t be tragic, except languages like C++ will allow you to throw an exception beyond the library boundary resulting in undefined behaviour. For each library function you wish to expose you must catch the exceptions manually and translate into a suitable error code. Calling a library poses a similar problem, but not as drastic. You’ll get simple return codes back but then must convert them to exceptions on your own. Such work could be handled by the language itself.

The boundary problem happens also when using the network, shared memory, or some other protocol. Simple error codes are trivial to serialize in both directions. Exceptions however require a bit of work, and generally involve the loss of information on the receiving side. While this loss of information is not tragic, it does reduce the value of using exceptions. A well designed framework could avoid all issues here, alas I have yet to see such an exception framework.

Not an isolated feature

Exceptions can’t exist as a feature on their own. To be used effectively there must be several other supporting language features. Without these features writing exception-safe code becomes burdensome, if not dangerous.

One of two critical features that needs to be offered is a “defer” mechanism or an “RAII” pattern. The defer mechanism is simply a way to say some code should be run once the current scope exists. RAII encapsulates such behaviour on a resource such that when the scope exits cleanup code can also be called. RAII is really nothing more than guaranteeing immediate object cleanup — many object based languages offer this, though not explicitly.

A “finally” block is also very helpful, though not essential. Going even further, “finally” is not sufficient to adequately support exceptions: you need one of defer and RAII as well. This is actually a significant problem with Java. While you certainly can use finally to write exception safe code, it suffers from the bulky syntax problems and being far removed from the initialization code it is cleaning.

Nothing is inherently bad with theses features. They do however increase the complexity of the language. Proper use of exceptions requires learning these additional features. A language with exceptions which fails to offer these features is deficient.

Bulky Syntax

When C++ added exceptions it included a “try” block as a way to catch exceptions. This perhaps seemed like a good idea at the time, since you’d like to indicate where you might be catching an exception. Now however it seems superfluous: any part of the code can throw an exception, and you might handle them anywhere, so why should you have to declare “try”, it should be implicit.

This leads to the somewhat bulky syntax of dealing with exceptions. The “catch” expression itself isn’t so large, but you can’t use it without a “try” and without full bracketed code. In a large block of code the overhead may not seem like much, but if you’re trying to handle an exception for a single function the overhead is very large. In this case your code becomes significantly more bloated than if you had used return codes.

It’s all or nothing / Legacy Code

A major force of resistance against exceptions is legacy code. Exceptions cannot be propagated through any code which is not exception safe. The use of exceptions thus implies that all code in the project must be exception safe. Any code which existed prior to this point would have to be scoured and modified to work with exceptions. In many cases this exception upgrading is as costly as rewriting the code — something few projects can afford to do.

You can certainly make new modules of your projects exception safe. You could even segregate the old code into libraries and treat it as such. You’ve not solved the issue however, you’ve just converted it to the library boundary issue. Though even if you don’t use exceptions, the tools, like RAII, finally blocks, and deferred, still offer a lot of utility.

Could a language, or compiler, feature ease this burden? Perhaps. As it could ease the burden of library boundaries it could also offer a similar feature within a module.

Too Many Exceptions

Not all functions can directly deal with all types of errors. The catch mechanism allows functions to deal only with those errors of interest to them. This is a good idea in theory, but in practice something went wrong. It only works if the number of exceptions is very low and they are well classified.

Unfortunately it seems that every library has defined its own set of exceptions, and every author has a different notion of how they should be structured. Documentation is also lacking in many places and it becomes difficult, if not impossible, to determine what an API can even throw. You end up being forced to catch all exceptions, defeating a key part of the typed catch system.

This is not a necessary problem with exceptions. A better exception hierarchy can be made, but it requires good forsight and can’t really be fixed after the fact.

The Checked Exception Mess

Along with exceptions came the idea of checked and unchecked exceptions. Some exceptions could be thrown implicitly while others had to be explicitly handled. These checked exceptions become part of the function signature and are rigidly enforced by the compiler. It seemed like a great idea for strict error handling. The problems with this approach are numerous however: at a minimum conflicting with some of the basic notions of object oriented programming and abstraction.

This is exemplified in Java where no program escapes the need to have a catch-all clause and simply rethrow them as a different exception type. This type of code brings no added value and just results in a lot of useless code. These wrappers further neuter the value of a type exception system since the true types are completely hidden.

Static Exceptions / Exceptions in Exceptions

While exceptions during the main program path can be readily handled, there is some problem when it comes to global variables, statics, and other startup initialization. To be fair however, this problem is not new to exceptions. Errors during initialization are a problem regardless of what mechanism is used. Though it is highly dissatisfying that a language would offer static initialization yet have no way to handle such errors.

Perhaps a related issue is dealing with exceptions which are thrown while already in an exception. C++ thought it was a good idea to simply call terminate. Again, dealing with an error in an error is not easy in any system, but one should think the behaviour is at least safely defined. In fact, since errors within errors are not infrequent, there should be a well defined and clear mechanism to deal with them.

Lacking proper support for static initialization and errors within errors leaves exceptions feeling incomplete.

Fancy Goto

An oft repeated argument I’ve seen against exceptions is that they are naught but glorified gotos. The label is perhaps misleading; the intent is to indicate it bypasses the strict imperative flow. Any statement in a program now has both a normal sequence (to the next statement) and an exception sequence which brings it somewhere else. For many people this alternate flow is considered harmful.

I’ve never seen this argument presented strongly enough to consider it a fully valid issue. Simple things like “break” and “continue” keywords can also alter flow. Likewise can a mid-function “return” also is a kind of “goto”. New language features like a “defer” statement is certainly a non-standard flow, as are destructors, or even the “finalize” handlers used in garbage collection. Even languages without exceptions have these alternate program flows.

25 Comments

  1. hacksoncode

    The point about exceptions being fancy gotos is really mostly about exceptions being *invisible* gotos. All of the other examples you give are explicit and visible, so someone reading the code can reason out, fairly locally, what may happen.

    A big problem with C++ exceptions, for example, is that they can often be caught in completely unpredictable places. Depending on the identity of the exception, it might be caught explicitly in local scope, or jump up to the caller, or the caller of that… or it might not be caught at all, in which case you have a crash with probable data loss.

    As a consequence, when you’re reviewing code, unless you examine every function that you call, and every function that those functions call, ad nauseum, and quite possibly all of your ancestor callers as well, you can’t even know which, if any, of the above invisible gotos might happen. The problem with exceptions (at least the C++ variant of them) is *non-locality* and readability.

    Now, of course, one might say the same about error codes, or the consequences of failing to check them… but at least you can see all of those possible consequences without going on a treasure hunt.

    1. mortoray

      They are not invisible. In exception code you have to assume any function can throw an exception at any time. There is nothing unpredictable about them. Aside from the cases I mention, what path an exception takes is well defined and not variable. Refer to my previous article: what you consider a problem, can be caught anywhere, I consider to be the most significant advantage of exceptions.

  2. Tanner

    Would just like to say the new Java 7 interface ‘AuotCloseable’ helps with getting rid of finally blocks.

    1. mortoray

      This does look helpful, kind of like a defer/RAII combined. I think the syntax of either defer or RAII would still be simpler.

  3. mcandre

    Exceptions are often unnecessary and cumbersome. Declarative programming offers a practical alternative–data types for encoding successful operations (Right thing) as well as errors (Left trace). This is known as the Either type, a value returned by an operation that can then be evaluated in order to determine the success or failure (and the successful value or error trace) by a simple case/switch expression.

    See OCaml, Haskell.

    1. mortoray

      Refer to my previous article, linked in the intro. There is a discussion about problems with the Haskell approach in the comments.

  4. agcwall

    You should look at D’s “scope” keyword, this allows an RAII-like “run this code when the scope exits” behaviour that you are looking for.

    Also, you should look at Haskell/ML algebraic data types. Checked exceptions are a way to mimic this in OO languages. Generally, a function which may throw a checked exception is really just one which should have multiple “cases” as a return value. It would be nice if OO languages had albegraic datatypes to alleviate the need for this. As far as I’m aware, in the OO-space, only Scala offers a comparable feature: case classes.

    I believe if you were better informed on functional languages, you wouldn’t have written this article…

  5. agcwall

    You should look at three things:

    1) D. Specifically, the “scoped” keyword. This provides the “run this code on scope exit” feature that you are looking for.
    2) Algebraic data types, ala Hindley-Milner type systems (Haskell, ML). These are mimiced in Java by checked exceptions. A function which throws checked exceptions should really just return a Sum type. In the OO-landscape, the closest thing I’m aware of is Scala’s “case classes”. However, most languages are not enlightened enough to provide this feature… and I believe Java’s checked exceptions are much more effective at reducing your bug-count than the unchecked equivalent… you just have to pay the price of sometimes rethrowing exceptions as different types, which I think is worth it.

    1. Andrzej Krzemieński

      “Algebraic data types, ala Hindley-Milner type systems (Haskell, ML). […] In the OO-landscape, the closest thing I’m aware of is Scala’s <>.”

      Maybe I am just incorrectly associating same names but with different meanings, but doesn’t C++ offer powerful enough library tools for you to build such algebraic tools yourself (I mean boost::tuple, boost::optional, boost::variant)?
      See these articles:

      http://cpp-next.com/archive/2010/07/algebraic-data-types/
      http://cpp-next.com/archive/2010/09/algebraic-data-types-in-c/

  6. itoctopus

    I have never, ever used exceptions. In my opinion, they are the cause of many bugs and they are highly indicative of a bad programmer. Maybe I’m wrong but that’s how I feel towards them…

  7. Martin

    I’ve always thought exceptions are a great programming construct, but I guess there is space for any opinion in intermess.

    1. mortoray

      My intent with this article was just to show the issues that exist with exceptions — I’m giving void to the dissenters. Despite these problems I am completely in favour of using exceptions. Though I do wish there was a more well-rounded solution.

  8. hdeshev

    My biggest problem with exceptions is that they are bound to the current thread of execution. For example, consider a naive try/catch block that surrounds a problematic piece of code with that code offloading an operation to another thread and raising an exception there. Pretty soon you end up in the ugly situation of catching exceptions on child threads and propagating them to a master thread. I don’t really want to do that. :-)

    That’s why I try to follow the Haskell school of thought with Maybe/Either computation result types. You can transparently perform computations on other threads and just check the return values when they become available. And watch out for third-party code that throw exceptions in the meantime…

    1. rbitz

      No it isn’t, it’s a problem in many language’s implementation. Oz propagates exceptions between threads just fine. This is probably because threads in Oz actually evaluate to something. That is you write something = thread(….) and then something is eventually bound to that value, or using something will raise an exception.

    2. Andrzej Krzemieński

      Note that in C++11 (or in Boost) you do have a solution for this simple multi-threading usage): function std::async. See these links

      http://www.justsoftwaresolutions.co.uk/threading/multithreading-in-c++0x-part-8-futures-and-promises.html
      http://www.corensic.com/Learn/Resources/ConcurrencyTutorialPartFour.aspx

      In short, you start the asynchronous execution in one call, and collect (or block on collecting) in the other call: this other (collection) call may throw an exception if the function executed in the spawned thread ended in exception (the same exception is re-thrown):

      auto future = std::async( bigTask1 ); 
      int ans2 = bi gTask2(); // synchronous
      int ans1 = future.get(); // may throw
      

      Also, I believe C++ has tools similar to Maybe/Either monads: boost::optional and boost::varint, although it does not have other monad mechanism that make using these easy.

  9. Andrzej Krzemieński

    — “Perhaps a related issue is dealing with exceptions which are thrown while already in an exception. C++ thought it was a good idea to simply call terminate. Again, dealing with an error in an error is not easy in any system, but one should think the behaviour is at least safely defined. In fact, since errors within errors are not infrequent, there should be a well defined and clear mechanism to deal with them.”

    I think that it is a bit of an injustice to the two layered exception handling in C++. First, under which circumstances std::terminate is called is very well defined. Second, calling std::trminate does not mean terminating the program: it just calls a callback that you provide for the second level of exception handling. See this post for examples of such handlers:

    http://akrzemi1.wordpress.com/2011/10/05/using-stdterminate/

    1. mortoray

      Yes, it is well defined, one case is throw exception in an exception. And while you can implement a terminate function you can’t do anything but exit the program, resuming is not possible — as indicated in your article (nicely written BTW).

      I don’t consider this a critical failure of C++, but I do consider it a valid complaint. I just think there should be a standard and clean way to handle error-in-error conditions without having the program exit.

    2. Andrzej Krzemieński

      I am sorry, I was replying to too many things and I quoted the wrong thing. I wanted to address this one:

      — “While exceptions during the main program path can be readily handled, there is some problem when it comes to global variables, statics, and other startup initialization. To be fair however, this problem is not new to exceptions. Errors during initialization are a problem regardless of what mechanism is used. Though it is highly dissatisfying that a language would offer static initialization yet have no way to handle such errors.”

      I believe that in cases where you cannot successfully initialize the program, you do not mind, and in fact even expect that it is not executed. In this case calling std::terminate appears the logical and desired thing to do.

    3. mortoray

      I don’t expect my program to start, that is true. If I need to provide some kind of interactive or recovery process I can dynamically create the statics.

      However I would expect a way to at least intercept the exceptions and provide useful error messages. Since “main” has not yet been called there is no opportunity to call “set_terminate”, and thus you have no chance to intercept exceptions from statics.

  10. sapphirepaw

    OK, I read both articles, and I am curious if you’re familiar with Lisp’s condition system. It breaks exception handling into more parts (and I hope I have this right; I don’t have all of lisp loaded into my head this morning):

    * Conditions: like Exception, that indicates what went wrong.
    * Restart functions: like the code inside of a catch block, providing code to be run in the context of the function that wants to handle the error. (In particular, `(return foo)` inside of a restart function doesn’t just end the restart, it makes _the function establishing that restart_ return foo.)
    * Handlers: somewhat like catch dispatching mechanisms, they examine the condition and available restarts, then either pick one or punt.
    * Unwind protect: like finally, I think. I’m fuzzy on the details of that.

    In your earlier example, `parse_int` could establish a restart with code like `(return 0)`, and raise a `parse-error` if it encounters an invalid line. Then code somewhere above `sum_file` would have a handler that could invoke that restart, and the effect would be to ignore invalid lines. The handler can also print if it wants, or maybe count the errors and raise `invalid-file` if it sees parse-error raised 3 times, or just exit the whole program. (Of course a stack of handlers and restarts can be built up in more complex code.)

    This is all enabled because raising a condition is essentially calling _down_ so that none of the stack is discarded. When the handler runs, the stack is something like `main – sum_file – parse_int – signal – the_handler` which makes it trivial for the handler defined in main to get “back” to `parse_int`, whereas putting try/catch in main would mean your stack looked like `main` and you may have leaked a file handle.

    1. mortoray

      I’ve reviewed the lisp condition thoroughly and think I understand the basics now. While the “restart” function is presented as a great thing, I just don’t see the huge value in it. In order to use a restart the condition handler has to have intimate knowledge of how the called code actually works. It has to know a lot in order to decide what the exception means and whether a restart is possible. This will naturally limit its use to directly within he component where the exception was thrown. Given this, I find it is much cleaner to just have a flags based system, or a callback, and thus a generic mechanism is not needed.

      I believe the lisp condition system thus becomes essentially a C++ like system because the “restart” ability will simply not be usable to any great depth through the stack.

      I’ve been thinking about writing an article exactly on this topic, as oviously this short comment doesn’t fully convey what I mean.

  11. Andrzej Krzemieński

    — “I don’t expect my program to start, that is true. If I need to provide some kind of interactive or recovery process I can dynamically create the statics.

    However I would expect a way to at least intercept the exceptions and provide useful error messages. Since «main» has not yet been called there is no opportunity to call «set_terminate», and thus you have no chance to intercept exceptions from statics.”

    It is possible to register a terminate handler befor main starts. You need to eploit the nature of C++ rules that any code whatsoever can be executed before main starts if it is part of initializing a global object. Here is one example:

    const auto installed{ std::set_terminate(&handler) };
    	 
    int main() {
      // run...
    }
    

    Although this solution is far from the ideal: (1) it looks more like a hack and (2) some order-of-global-initialization problems may still cause that pieces of code are executed before your handler’s installation.

    I see your point.

    1. Ricardo Santos

      At least with a goto you have an idea of from where the code will jump.

      Thus I believe that exceptions are worse than goto.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s