128

I've been pondering this problem for a while now and find myself continually finding caveats and contradictions, so I'm hoping someone can produce a conclusion to the following:

Favour exceptions over error codes

As far as I'm aware, from working in the industry for four years, reading books and blogs, etc. the current best practice for handling errors is to throw exceptions, rather than returning error codes (not necessarily an error code, but a type representing an error).

But - to me this seems to contradict...

Coding to interfaces, not implementations

We code to interfaces or abstractions to reduce coupling. We don't know, or want to know, the specific type and implementation of an interface. So how can we possibly know what exceptions we should be looking to catch? The implementation could throw 10 different exceptions, or it could throw none. When we catch an exception surely we're making assumptions about the implementation?

Unless - the interface has...

Exception specifications

Some languages allow developers to state that certain methods throw certain exceptions (Java for example, uses the throws keyword.) From the calling code's point of view this seems fine - we know explicitly which exceptions we might need to catch.

But - this seems to suggest a...

Leaky abstraction

Why should an interface specify which exceptions can be thrown? What if the implementation doesn't need to throw an exception, or needs to throw other exceptions? There's no way, at an interface level, to know which exceptions an implementation may want to throw.

So...

To conclude

Why are exceptions preferred when they seem (in my eyes) to contradict software best practices? And, if error codes are so bad (and I don't need to be sold on the vices of error codes), is there another alternative? What is the current (or soon to be) state of the art for error handling that meets the requirements of best practices as outlined above, but doesn't rely on calling code checking the return value of error codes?

5
  • 15
    I don't get your argument about leaky abstractions. Specifying what exception a particular method may throw is part of the interface specification. Just as the type of the return value is part of the interface specification. Exceptions just don't "come out of the left side" of the function, but they're still part of the interface.
    – deceze
    May 2, 2012 at 12:23
  • 1
    @deceze How can an interface possibly state what an implementation may throw? It may throw all the exceptions in the type system! And that many languages don't support exception specifications suggest they're pretty dodgy
    – RichK
    May 2, 2012 at 12:28
  • 1
    I'd agree that it can be tricky to manage all the different exceptions a method may throw, if it itself uses other methods and does not catch their exceptions internally. Having said that, it's not a contradiction.
    – deceze
    May 2, 2012 at 12:36
  • 2
    The leaky assumption here is that code can handle an exception when it escapes the interface boundary. That's very unlikely, given that it doesn't know nearly enough about what actually went wrong. All it knows is that something went wrong and the interface implementation didn't know how to deal with it. The odds that it can do a better job are poor, beyond reporting it and terminating the program. Or ignoring it if the interface isn't critical to proper program operation. An implementation detail that you can't and shouldn't encode in an interface contract. May 2, 2012 at 13:17
  • The system is made of objects exchanging messages. There are no exceptions to that architecture.
    – DBJDBJ
    Feb 5, 2021 at 8:09

11 Answers 11

34

First of all, I would disagree with this statement:

Favour exceptions over error codes

This is not always the case: for example, take a look at Objective-C (with the Foundation framework). There the NSError is the preferred way to handle errors, despite the existence of what a Java developer would call true exceptions: @try, @catch, @throw, NSException class, etc.

However it is true that many interfaces leak their abstractions with the exceptions thrown. It is my belief that this is not the fault of the "exception"-style of error propagating/handling. In general I believe the best advice about error handling is this:

Deal with the error/exception at the lowest possible level, period

I think if one sticks to that rule of thumb, the amount of "leakage" from abstractions can be very limited and contained.

On whether exceptions thrown by a method should be part of its declaration, I believe they should: they are part of the contract defined by this interface: This method does A, or fails with B or C.

For example, if a class is an XML Parser, a part of its design should be to indicate that the XML file provided is just plain wrong. In Java, you normally do so by declaring the exceptions you expect to encounter and adding them to the throws part of the declaration of the method. On the other hand, if one of the parsing algorithms failed, there's no reason to pass that exception above unhandled.

It all boils down to one thing: Good interface design. If you design your interface well enough, no amount of exceptions should haunt you. Otherwise, it's not just exceptions that would bother you.

Also, I think the creators of Java had very strong security reasons to include exceptions to a method declaration/definition.

One last thing: Some languages, Eiffel for example, have other mechanisms for error handling and simply do not include throwing capabilities. There, an 'exception' of sort is automatically raised when a postcondition for a routine is not satisfied.

15
  • 14
    +1 for negating "Favour exceptions over error codes." I've been taught that exceptions are well and good, so long as they are, in fact, exceptions and not the rule. Calling a method which throws an exception to determine whether or not a condition is true is extremely bad practice.
    – Neil
    May 3, 2012 at 12:59
  • 5
    @JoshuaDrake: He's definitely wrong. Exceptions and goto are very different. For example, exceptions always go in the same direction- down the call stack. And secondly, protecting yourself from surprise exceptions is exactly the same practice as DRY- for example, in C++, if you use RAII to ensure cleanup, then it ensures cleanup in all cases, not just exception but also all normal control flow. This is infinitely more reliable. try/finally accomplishes something somewhat similar. When you properly guarantee cleanup, you don't need to consider exceptions as a special case.
    – DeadMG
    May 3, 2012 at 16:39
  • 5
    @JoshuaDrake Sorry, but Joel is way off there. Exceptions are not the same as goto, you always go at least one level up the call stack. And what do you do if the level above can't immediately deal with the error code? Return yet another error code? Part of the problem with error does is that they CAN be ignored, leading to worse problems than the throwning of an exception.
    – Andy
    May 4, 2012 at 1:56
  • 29
    -1 because I completely disagree with "Deal with the error/exception at the lowest possible level" - that's wrong, period. Deal with errors/exceptions at the appropriate level. Quite often that is a much higher level, and in that case, error codes are a huge pain. The reason to favor exceptions over error codes is that they allow you to choose freely at which level to deal with them without impacting the levels in between. May 4, 2012 at 6:56
  • 2
    @Giorgio: see artima.com/intv/handcuffs.html - especially page 2 and 3. May 7, 2012 at 9:34
29

I would just like to note that exceptions and error codes aren't the only way to deal with errors and alternate code paths.

Out of the top of the mind, you can have an approach like the one taken by Haskell, where errors can be signaled via abstract data types with multiple constructors (think discriminated enums, or null pointers, but typesafe and with the possibility of adding syntatic sugar or helper functions to make the code flow look good).

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail is a function that returns a value wrapped in a Maybe. It works like a nullable pointer, but the do notation guarantees that the whole thing evaluates to null if any of a, b, or c fail. (and the compiler protects you from making an accidental NullPointerException)

Another possibility is passing an error handler object as an extra argument to every function you call. This error handler has a method for each possible "exception" that can be signaled by the function you pass it to, and can be used by that function to treat the exceptions where they occur, without necessarily having to rewind the stack via exceptions.

Common LISP does this, and makes it feasible by having syntatic support (implicit arguments) and having the built-in functions follow this protocol.

3
  • 1
    That's really neat. Thanks for answering the part about alternatives :)
    – RichK
    May 3, 2012 at 16:34
  • 2
    BTW, exceptions is one of the most functional parts of modern imperative languages. Exceptions form a monad. Exceptions use patter matching. Exceptions help imitate applicative style without actually learning what Maybe is.
    – 9000
    May 3, 2012 at 17:28
  • I was reading a book about SML recently. It mentioned option types, exceptions (and continuations). The advice was to use option types when the undefined case is expected to occur rather often, and to use exceptions when the undefined case occurs very seldom.
    – Giorgio
    Jun 13, 2013 at 17:33
10

Yes, exceptions can cause leaky abstractions. But are error codes not even worse in this regard?

One way to deal with this problem is to have the interface specify exactly which exceptions can be thrown under what circumstances and declare that implementations must map their internal exception model to this specification, by catching, converting and re-throwing exceptions if necessary. If you want a "prefect" interface, that's the way to go.

In practice, it's usually sufficient to specify exceptions that are logically part of the interface and which a client may want to catch and do something about. It's generally understood that there can be other exceptions when low-level errors happen or a bug manifests, and which a client can only handle generally by showing an error message and/or shutting down the application. At least the exception can still contain information that helps diagnosing the problem.

In fact, with error codes, pretty much the same thing ends up happening, just in a more implicit fashion, and with far more likelihood of information getting lost and the app ending up in an inconsistent state.

6
  • 1
    I do not understand why an error code can cause a leaky abstraction. If an error code is returned instead of a normal return value, and this behaviour is described in the specification of the function / method, then IMO there is no leak. Or am I overlooking something?
    – Giorgio
    May 4, 2012 at 21:52
  • If the error code is specific to the implementation while the API is supposed to be implementation-agnostic, then specifying and returning the error code leaks unwanted implementation details. Typical example: a logging API with a file-based and a DB-based implementation, where the former may have a "disk full" error, and the latter a "DB connection refused by host" error. May 4, 2012 at 22:04
  • I understand what you mean. If you want to report implementation specific errors then the API cannot be implementation agnostic (neither with error codes nor with exceptions). I guess the only solution would be to define an implementation-agnostic error code like "resource not available", or to decide that the API is not implementation-agnostic.
    – Giorgio
    May 4, 2012 at 22:31
  • 2
    @Giorgio: Yes, reporting implementation-specific errors is tricky with an implementation-agnostic API. Still, you can do it with exceptions, because (unlike error codes) they can have multiple fields. So you can use the type of the exception to provide the generic error information (ResourceMissingException), and include an implementation-specific errorn code / message as a field. Best of both worlds :-).
    – sleske
    May 7, 2012 at 8:13
  • And BTW, that is just what java.lang.SQLException does. It has getSQLState (generic) and getErrorCode (vendor-specific). Now if only it had proper subclasses...
    – sleske
    May 7, 2012 at 8:15
6

Lots of good stuff here, I'd just like to add that we should all be wary of code which uses exceptions as part of normal control flow. Sometimes people get into that trap where anything which isn't the usual case becomes an exception. I've even seen an exception used as a loop termination condition.

Exceptions mean "something I can't handle here happened, need to go out to someone else to figure out what to do." A user typing invalid input isn't an exception (that should be handled locally by the input by asking again, etc.).

Another degenerate case of exception usage I've seen is people whose first response is "throw an exception." This is almost always done without writing the catch (rule of thumb: write the catch first, then the throw statement). In big applications this becomes problematic when an uncaught exception bubbles up from the nether-regions and blows up the program.

I'm not anti-exceptions, but they seem like singletons of a few years ago: used far too frequently and inappropriately. They're perfect for the intended use, but that case is not as broad as some think.

5

Leaky abstraction

Why should an interface specify which exceptions can be thrown? What if the implementation doesn't need to throw an exception, or needs to throw other exceptions? There's no way, at an interface level, to know which exceptions an implementation may want to throw.

Nope. Exception specifications are in the same bucket as return and argument types- they are part of the interface. If you can't conform to that specification, then don't implement the interface. If you never throw, then that's fine. There's nothing leaky about specifying exceptions in an interface.

Error codes are beyond bad. They're terrible. You have to manually remember to check and propagate them, every time, for every call. This violates DRY, for a start, and massively blows up your error-handling code. This repetition is a far bigger problem than any faced by exceptions. You can never silently ignore an exception, but people can and do silently ignore return codes- definitely a bad thing.

6
  • Error codes can be easier to use if you have good forms of syntactic sugar or helper methods though, and in some languages you can make the compiler and type-system guarantee that you will never forget to handle an error code. As for the exception interface part, I think he was thinking about the notorious clunkyness of Java's checked exceptions. While they seem like a perfectly reasonable idea at first glance, they cause many painfull little problems in practice.
    – hugomg
    May 3, 2012 at 16:55
  • @missingno: That's because, as per usual, Java has a terrible implementation of them, not because checked exceptions are inherently bad.
    – DeadMG
    May 3, 2012 at 16:59
  • 1
    @missingno: What kinds of problems can be caused by checked exceptions?
    – Giorgio
    May 7, 2012 at 9:11
  • 1
    @Giorgio: Its very hard to foresee every kind of exception that a method might throw, since this needs to take into account other subclasses and code that has yet to be written. In practice people end up with ugly workarounds that throw away information, like reusing a system exceptions class for everything or having to catch and re-throw inner exceptions frequently. I have also heard that checked exceptions were a big hurdle when they were trying to add anonymous functions to the language.
    – hugomg
    May 7, 2012 at 15:01
  • 1
    @Giorgio: Checked exceptions don't interact well with the generic system, making it harder to define many sorts of useful higher-order functions. james-iry.blogspot.com.br/2012/02/…
    – hugomg
    May 7, 2012 at 20:04
3

Favour exceptions over error codes

  • Both should coexist.

  • Return error code when you anticipate certain behaviour.

  • Return exception when you did not anticipate some behaviour.

  • Error codes are normally associated with a single message, when exception type remains, but a message may vary

  • Exception has a stack trace, when error code doesn't. I don't use error codes to debug broken system.

Coding to interfaces not implementations

This might be specific to JAVA, but when I declare my interfaces I do not specify what exceptions might be thrown by an implementation of that interface, it just doesn't make sense.

When we catch an exception surely we're making assumptions about the implementation?

This is entirely up to you. You may try and catch a very specific type of exception and then catch a more general Exception. Why not let exception propagate up the stack and then handle it? Alternatively you can look at aspect programming where exception handling becomes a "pluggable" aspect.

What if the implementation doesn't need to throw an exception, or needs to throw other exceptions?

I don't understand why is it a problem for you. Yes, you may have one implementation that never fails or throws exceptions and you may have a different implementation that constantly fails and throws exception. If that's the case, then don't specify any exceptions on the interface and your problem is solved.

Would it change anything if instead of exception your implementation returned a result object? This object would contain outcome of your action along with any errors/failures if any. You can then interrogate that object.

2

Well Exception handling can have their own interface implementation. Depending on the the type of the exception thrown, perform the desired steps.

The solution to your design problem is to have two interface/abstraction implementations. One for the functionality and the other for exception handling. And depending on the type of the Exception caught, call appropriate exception type class.

Implementation of Error codes is an orthodox way of handling exception. It is like usage of string vs. string builder.

1
  • 4
    in other words: implementations throw subclasses of the exceptions defined in the api. May 2, 2012 at 12:47
2

Leaky abstraction

Why should an interface specify which exceptions can be thrown? What if the implementation doesn't need to throw an exception, or needs to throw other exceptions? There's no way, at an interface level, to know which exceptions an implementation may want to throw.

In my experience, the code that receives the error (be it via exception, error code or anything else) would not normally care for the exact cause of the error - it would react the same way to any failure, except for possible reporting of the error (be it an error dialog or some kind of log); and this reporting would be done orthogonally to the code that called the failing procedure. For example, this code could pass the error to some other piece of code that knows how to report specific errors (e.g. format a message string), possibly attaching some context information.

Of course, in some cases it is needed to attach specific semantics to errors and react differently based on which error occured. Such cases should be documented in the interface specification. However, the interface may still reserve the right to throw other exceptions with no specific meaning.

1

IM-very-HO exceptions are to be judged on a case by case basis, because by breaking control flow they will increase the actual and perceived complexity of your code, in many cases unnecessarily so. Putting aside the discussion related to throwing exceptions inside your functions - which may actually improve your control flow, if one is to look at throwing exceptions through call boundaries, consider the following:

Allowing a callee to break your control flow may not provide any real benefit, and there may be no meaningful way to deal with the exception. For a direct example, if one is implementing the Observable pattern (in a language such as C# where you have events everywhere and no explicit throws in the definition), there is no actual reason to let the Observer break your control flow if it crashes, and no meaningful way to deal with their stuff (of course, a good neighbor should not throw when observing, but nobody's perfect).

The observation above can be extended to any loosely coupled interface (as you pointed out); I think it's actually a norm that after creeping up 3-6 stack frames, an uncaught exception is likely to end up in a section of code which either:

  • is too abstract to deal with the exception in any meaningful way, even if the exception itself is upcasted;
  • is performing a generic function (couldn't care less about why you failed, such as a message pump, or the observable);
  • is specific, but with a different responsibility, and it really shouldn't worry;

Considering the above, decorating interfaces with throws semantics is only a marginal functional gain, because a lot of callers thru interface contracts would only care if you failed, not why.

I would say it then becomes a question of taste and convenience: your primary focus is gracefully recovering your state in both the caller and the callee after an "exception", therefore, if you have a lot of experience in moving error codes around (coming from a C background), or if you're working in an environment where exceptions can turn evil (C++), I don't believe that throwing stuff around is so important for nice, clean OOP that you can't rely on your old patterns if you're uncomfortable with it. Especially if it leads to breaking SoC.

From a theoretical perspective, I think a SoC-kosher way of handling exceptions can be derived directly from the observation that most of the times the direct caller only cares that you failed, not why. The callee throws, someone very close above (2-3 frames) catches an upcasted version, and the actual exception is always sunk to a specialized error handler (even if only tracing) - this is where AOP would come in handy, because these handlers are likely to be horizontal.

1

I find that exceptions allow to write a more structured and concise code for reporting and handling errors: using error codes requires checking return values after every call and deciding what to do in case of an unexpected result.

On the other hand I agree that exceptions reveal implementation details that should be hidden to the code invoking an interface. Since it is not possible to know a priori which piece of code can throw which exceptions (unless they are declared in the method signature like in Java), by using exceptions we are introducing very complex implicit dependencies between different parts of the code, which is against the principle of minimizing dependencies.

Summarizing:

  • I think exceptions allow a cleaner code and a more aggressive approach to testing and debugging because uncaught exceptions are much more visible and difficult to ignore than error codes (fail soon).
  • On the other hand, uncaught exception bugs that are not discovered during testing can appear in a production environment in the form of a crash. In certain circumstances this behaviour is not acceptable and in this case I think using error codes is a more robust approach.
4
  • 2
    I disagree. Yes, uncaught exceptions can crash an application, but unchecked error codes can as well - so it's a wash. If you use exceptions properly, the only uncaught ones will be the fatal ones (like OutOfMemory), and for these, crashing right away is the best you can do.
    – sleske
    May 7, 2012 at 8:19
  • The error code is part of the contract between the caller m1 and the callee m2: the possible error codes are defined in the interface of m2 only. With exceptions (unless you are using Java and declaring all thrown exceptions in method signatures) you have an implicit contract between the caller m1 and all methods that can be called by m2, recursively. So of course it is a mistake not to check a returned error code, but it is always possible to do it. On the other hand, it is not always possible to check all exceptions thrown by a method, unless you know how it is implemented.
    – Giorgio
    May 7, 2012 at 8:25
  • First: You can check all exceptions - just do "Pokemon exception handling" (gotta catch them all - i.e. catch Exception or even Throwable, or equivalent).
    – sleske
    May 7, 2012 at 8:46
  • 1
    In practice, if the API is properly designed, it will specify all exceptions that the client can handle meaningfully - these need to be caught specifically. These are the equivalents of error codes). Any other exception means "internal error", and the application will need to shut down, or at least shut down the corresponding subsystem. You can catch these if you want to (see above), but you usually should just let them bubble up. That "bubbling up" is the main advantage of using exceptions. You can still catch them farther up, or not, depending on needs.
    – sleske
    May 7, 2012 at 8:48
-1

Right-biased Either.

It can't be ignored, it must be handled, it's completely transparent. And IF you use the correct left handed error type it conveys all the same information as a java exception.

Downside? Code with proper error handling looks disgusting (true of all mechanisms).

2
  • this doesn't seem to offer anything substantial over points made and explained in prior 10 answers. Why bumping two years old question with stuff like that
    – gnat
    Dec 22, 2014 at 13:53
  • Except no one here mentioned the right biased either. hugomg got close talking about haskell, however Maybe is a shit error handler as it leaves no explanation of why the error occurred, nor any direct method with which to recover, and call-backs are one of the greatest sins in control flow design. And this question came up on google.
    – Keynan
    Dec 23, 2014 at 23:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.