Errors: Common Misconceptions

From Matt Morris Wiki
Jump to navigation Jump to search

Programming - Errors


General

Exceptions are bad because they add hidden code paths

Many people have a sneaking feeling that exceptions make it too hard to reason about code, because they introduce extra, 'hidden' code paths. Joel Spolsky put this point of view particularly well.

However, the extra code paths are a consequence of the main advantage of exceptions: that they are "implicit", providing an automatic return up the call chain, as opposed to the "explicit" nature of return codes. Pointing out extra code paths in isolation is missing the full picture. The alternative to these extra code paths is not only to have programmers manually write code to ferry return values back up all the calling levels in an application, but for them also to make sure that any operation that might fail can have that failure expressed by a return value. That implies that object constructors and overridden operators cannot have any non-trivial code in them.

Modern object-oriented programming languages are geared towards using implicit propagation of errors via exception-handling. Their syntax makes it very difficult to avoid exceptions altogether. However, if (for whatever reason) the "implicit" nature of exceptions is too much of a drawback, there is an alternative - use procedural programming, where return values are always available. For instance, Linux kernel hackers still confine themselves very much to C rather than C++.

Exceptions are good because they do all the work of error handling for you

Error handling is trying to achieve a lot , and throwing an exception when a problem arises is only the start of the story. Following some general principles will help, but you need to also have a feel for why you're following them.

Exceptions are too slow

Whether speed may be an issue depends on which aspect of using exceptions is being discussed: adding "try" and "catch" clauses, or actually throwing an exception.

Adding "try"-"catch" clauses to functions typically has little or no effect on speed, particularly for languages that already record stack frames (Java, .NET). Some extra book-keeping is required for C++ applications. So if you think that using "try" clauses might be slowing your application down, profile before you take any action. Unless you are writing a hard real-time system, you are almost certainly prematurely optimizing your code.

By contrast, throwing (and catching) an exception does take an appreciable amount of time. This is why people say that exceptions should be used only in "exceptional conditions". So if you are routinely triggering many exceptions at the centre of a loop, you may well be able to speed things up by checking whether the offending operation will be valid before you attempt it. However, if an error will probably cause a transaction involving a large amount of surrounding code and resources to be aborted, then the cost of throwing an exception will probably be the least of your worries.

C++

Cargill proved exception handling and C++ templates didn't mix in 1994

In 1994, the C++ Report published the article Exception Handling: A False Sense Of Security by Tom Cargill. This pointed out problems with a "Stack" template class, and argued that exception safety and templated container classes would be a very difficult mix to get to work, and might not even be possible to use together.

This article is still occasionally used as "proof" that C++ templates and exceptions don't mix. At the time, the article was very important in focusing debate on how to provide exception-safe generic components. However, several different exception-safe implementations of the C++ standard library have now provided practical demonstration that C++ templates and exceptions can indeed be used together.

So what was the solution to the problem in the original article? It turns out that the underlying problem stemmed from the particular interface that the "Stack" template had. It was not possible to get the top element without calling a "Pop()" method that removed it from the stack, and returned a copy. If the copy threw an exception, the top element would be forever lost. The answer is to separate "accessor" functions, that provide information about a class's state, and "mutator" functions, that change it. The "Stack" template's "Pop" method combined both types of operation. By providing a "void pop()" method that simply removed the top element (the mutator), and a "const T& top() const" method that returned the top element (the accessor), the problems in the article can be circumvented.

For the facts on exception safety and C++ templates, read David Abraham's document .

You shouldn't let exceptions escape from C++ constructors

A lot of C++ code that handles exceptions poorly does so because people are unsure about the behaviour of the language when an exception is thrown in an object constructor. Indeed, many feel that the outcome is somehow officially "ill-defined" - a view that might be partly due to the buggy behaviour of some compilers back in the mid-90's, and partly due to the worry that a destructor might be called on an object that has not been fully constructed.

In fact, the behaviour is well-defined, and well-supported. If an object constructor throws an exception, then destructors are called for all fully-constructed sub-objects, in reverse order of their construction.

Not only is the outcome of throwing an exception in a constructor well-defined, but it is what programmers need to do to write robust, idiomatic C++ code .