Errors: Return Values Or Exceptions?

From Matt Morris Wiki
Jump to navigation Jump to search

Programming - Errors


The Controversy

There are quite a few essays on the web on whether to use return values or exceptions for error handling. They draw drastically varying conclusions.

Some are deeply sceptical of the value of exceptions:

Joel Spolsky considers exceptions to be no better than "goto's", and says that for mission-critical code, exceptions are extremely dangerous.

Some point out potential problems with exceptions:

Raymond Chen says writing correct code in the exception-throwing model is in a sense harder than in an error-code model, since anything can fail, and you have to be ready for it.

Tom Cargill's essay Exception Handling: A False Sense Of Security (C++ Report, Vol 6, No 9, Nov-Dec 1994) pointed out problems with a templated C++ "stack" class, challenging readers to provide an exception-correct solution. The problems it posed have now been solved, but it's still a classic article.

Some argue that exceptions can be the better choice:

Ned Batchelder says exceptions are better than status returns for handling errors as they pass from layer to layer in software.

The C2 Wiki Page UseExceptionsInsteadOfErrorValues says that one should Use Exceptions to represent the possible exceptional conditions that can go wrong.

A Comparison

How can we decide between return values and exceptions?

Firstly, note that java's checked exceptions are discussed at a later point: this discussion contrasts return values and "unchecked" exceptions.

Secondly, while it is true that some people would deny that exceptions are a valid choice under any circumstances, this is an increasingly rare position. This discussion tacitly assumes that it is possible to write code that performs properly in the presence of exceptions, provided that certain techniques are used.

Here are some of the essential differences between the two methods:

Return values are explicit. They are passed along by commands written in the source code, with the programmer checking return values and passing them back up the call chain. Exceptions are implicit. They propagate automatically, with no action required on the part of the programmer.

Return values are cheaper than exceptions. The act of throwing an exception and unrolling a call chain is always going to be slower than simply returning a value from a function, since it involves extra bookmarking.

Exceptions promote looser coupling than return values. This arises from the "implicit" nature of exceptions. Since they do not need to be explicity moved through the code between the error and the handler, that code does not need to know the type of the exception.

Guidelines

If Your Language Doesn't Support Exceptions, Use Return Values

Sometimes your form of error handling is dictated by the language. For instance, C does not support exceptions. You can use setjmp() and longjmp() to get part of the way there (albeit without stack rollback), but that's all. The natural way for C to deal with errors is by return values, so that is the best thing to do in C.

Whatever language you're using, you should translate errors at language boundaries to provide other languages with error forms that work well for them.

If You Can't Return A Value, Throw An Exception

A constructor call like this:

    Foo foo(bar); // what if this fails? 

- cannot return an error value. So an exception is the only way to signal a problem. This is true of any language constructs that don't return values, eg overloaded operators in C++.

Sometimes, when a value cannot be returned, people try to get around this issue by only doing very simple operations. They put work that might fail inside a later method call, so a return value can be checked and an exception avoided. For instance, the "heavy work" of initialising an object might be moved out of a constructor and into a seperate "init()" function:

    Foo foo(); // this does very little
    int retCode = foo.init(bar); // this does the real work 

Doing this is an attempt to get around the natural mechanics of the language in question - it's far better to understand how to write code that works in the presence of exceptions instead.

If The Error May Not Propagate At All, Use A Return Value

If you are not sure whether you will need to propagate at all, then use a return value first off. This avoids paying a cost for something (ease of propagation) that you might not use. For instance, low-level APIs work on the assumption that errors will not necessarily be propagated very far. If a socket fails to connect, the user will get an error code, and then might simply try another address from an array of potential servers. Throwing an exception for a failed socket connection would be overkill.

If The Error Will Probably Propagate Many Levels, Use An Exception

If, for instance, a business report program finds it is missing a mandatory field, it has little choice but to raise an error there and then. The best handler will probably be whatever invoked the report - this could be dozens of call levels above the point where the error happens. An exception is a good match for this kind of situation. Having to move a return value back up through the call chain would make the code less readable for no gain.

If Exceptions Are Too Slow, Use Return Values

If the error is likely to happen 1000 times a second, even if it's only happening in 5 per cent of calls, then consider using a return value rather than throwing an exception. If speed considerations are looking important, however, don't guess. Use a profiler with both ways, and see how much difference it makes.

Exceptions Are Better For Complex Errors (Especially In C++)

Return values are most often numeric status codes. However, it is possible to use more complex return values. In languages such as Java, C# and Python, one can use an "Object" return value to hold any information desired. This means that it's possible to propagate complex error information via return values in such languages, without the propagating code having to know the type of the error information, as long as you don't mind casting to and from "Object".

This is not so in C++, which lacks a common object subclass, meaning that an error class needs to have its declaration known by all the code that might propagate it. This means that, in C++, return values do not lend themselves very readily to propagating complex information across widely separated code.

Be Prepared To Use Both Error Forms

Many large systems will have areas playing the role of low-level APIs, and areas where exceptions are a better match. There is nothing wrong with providing two versions of a method - one which returns an error value if it fails, and one which throws an exception.