Exceptions vs. status returns

Broadly speaking, there are two ways to handle errors as they pass from layer to layer in software: throwing exceptions and returning status codes. Almost everyone agrees that exceptions are the better way to do it, but some people still prefer status returns. This article shows why exceptions are better.

The examples here are in C++, because that is the primary battlefield for this argument. Older languages like C don’t have exceptions as a real possibility, and newer languages like Java, Python, Ruby, and even Perl provide facilities for exceptions, without the cultural heritage that encouraged status returns in the first place.

Clean code

Exceptions let you leave error handling code out of much of your code. Exceptions are transmitted automatically through layers that have no knowledge of them, so you can write useful code that has no error handling logic at all. This helps keep the code straightforward and usable.

For example, compare two ways of writing the same simple procedure. With status returns:

STATUS DoSomething(int a, int b)
{
    STATUS st;
    st = DoThing1(a);
    if (st != SGOOD) return st;
    st = DoThing2(b);
    if (st != SGOOD) return st;
    return SGOOD;
}

And then with exceptions:

void DoSomething(int a, int b)
{
    DoThing1(a);
    DoThing2(b);
}

Even if the first version is rewritten with macros to hide much of the status return scaffolding, it’s uglier and more cluttered than with exceptions:

#define TRY(s)  { STATUS st = (s); if (st != SGOOD) return st; }

STATUS DoSomething(int a, int b)
{
    TRY(DoThing1(a));
    TRY(DoThing2(b));
    return SGOOD;
}

If the code were more complex than this, the extra noise from the error handling would be much worse. Exceptions keep the code clean.

Valuable channels

With status returns, a valuable channel of communication (the return value of the function) has been taken over for error handling. Some methods are so simple, and conceptually return a value, so human temptation takes over, and the method is written to return the value rather than a status code. “It’s a simple function, it can’t fail, this will be more convenient”. Over time, the code grows, and the method gets larger, calling more helper functions, and pretty soon it can fail, but it has no way to express it.

So the return value is overloaded: “If it fails, it returns NULL”. Now we have more than one convention in the code (probably more, because your numeric getters will return -1 if they fail), and everything still has to be checked. Your previously failsafe function now has to have all of its call sites updated to check for the new error value.

Why use a technique that begs to be subverted in the first place? Exceptions stay in the background, leaving the most useful tools for the successful cases. Functions can return values, and still have a useful way to fail.

Richer error information

Status returns are typically an integer. A few bits are reserved for flags (for example, a severity indication), and the rest are a large set of enumerated failures. This is a fairly impoverished error value. For example, suppose the failure is that a file could not be found. Which file? A status return can’t convey that much information.

Other channels can be developed to carry supplemental information, but typically they are not. In a status return world, the best you can hope for is for the failure site to log a message, and then to return the status. This is simplistic: perhaps the caller knows that it is OK for a file to be missing. If the file opener logs a message, the log will be incorrect (an error will be printed when nothing is wrong). If the file opener doesn’t log a message, the caller (or his caller) may have no way of getting the detail on the error needed to print a useful message.

Exceptions are instances of classes, and as such can carry as much information as they need to accomplish their task. Because they can be subclassed, different exceptions can carry different data, allowing for a very rich zoology of error messages.

As an example of the richness exceptions can express, Java defines exceptions as containing a reference to another exception, so that chains of effect can be constructed and retained as the exception moves up through layers of handling. This allows for rich diagnostic information: exception B occurred here, and was caused by exception A occurring there.

Implicit code

Status returns can’t even be used with some functions. For example, constructors don’t have an explicit return type, and so cannot return a status code. Destructors may not even be explicitly called, never mind that they don’t have a return value. In C++, operator overloading and implicit casting (controversial though they are) are other forms of implicit function calls that cannot be checked for return values.

None of these functions can be given status returns. If you don’t use exception handling, you have to either come up with some other way of marking errors within them, or pretend that they cannot fail. Simple code may be fail-safe, but code always grows, adding opportunities for failure. Without a way to express the failure, your system will only grow more error-prone and mysterious.

Sins of omission

Consider what happens in each technique when a coder slips up.

When a status return goes unchecked, a failure in the called routine will be undetected. The code will continue executing as if that operation had succeeded. There’s no way to characterize what might happen at that point, but it is clear that no one will know that an error had occurred. Perhaps the code will visibly fail later on, but that could be many operations later. How will you trace the problem back to the original failure?

If an exception goes uncaught, the exception will travel upward in the call stack either to a higher catch block, or to the uppermost frame where the operating system will do something with it, usually present it to the user. This is not good behavior for the system, but it is visible. You will see an exception, you will be able to diagnose where it was thrown, and where it should have been caught, and you will be able to fix the code.

I’m not covering here the problem of failing to announce a problem (either by returning a failure code or throwing an exception), because that case is a wash for the two techniques. Both are prone to it, and both will fail in similar ways.

So for human error, it comes down to this: human error with status returns results in invisible problems, human error with exceptions results in visible problems. Which would you rather have?

Counter-arguments

Joel Spolsky has argued that status returns are better. His main argument is that exceptions “are significantly worse than gotos”:

  1. They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn’t reveal potential bugs.
  2. They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don’t catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn’t think about.

This seems like a reasonable argument until you work out what the code would look like with status returns. We aren’t arguing here whether functions should be able to fail or not, just what should happen when they do. So all of those possible exit points for a function are still possible exit points, but you have to check the status returns explicitly, and return from the function. So you’ve traded implicit complexity for explicit complexity, which may not be a good trade. With explicit complexity, you can’t see the forest for the trees. Your code is cluttered with the explicit handling of error statuses.

When presented with this explicit complexity, programmers will strive to reduce it. They have two primary ways to do it: hide the error handling, or omit the error handling.

Hiding the error handling is what we did above with the TRY macro. This simply turns your explicit code paths back into implicit code paths, but with the annoying noise of TRY littered all over the place. This is certainly no victory for the “explicit code paths are better” argument for status returns. If you’re going to use implicit code paths, at least use exceptions to create them so you have some modern tools at your disposal.

The other solution to the overwhelming explicit complexity in code paths is to simply avoid checking the error returns. Developers will convince themselves (through code inspection or system-level understanding or just plain bad logic) that a certain function always succeeds. This leads to errors going unchecked, which leads to invisible problems.

In a nutshell

Status returns are difficult to use. There are places where they are impossible to use. They hijack a useful channel of communication. For all of these reasons, it is easy and tempting to not use them. When not used, they produce silent failures in your system.

Status returns are inferior to exceptions. All modern programming systems provide tools for exception handling. Use them.

See also

Comments

[gravatar]
Coincidence? Yesterday I had to fix a method that trapped an exception and returned a status instead. Nasty bit of of work would'nt allow the application that was using the library to handle the exceptions on an individual merit.
[gravatar]
Joel has great software-business/project-manager intuition and smarts, but I fear he may be lacking in the pure-technical department. He argues that this argument devolves into a religious debate, like so many other debates in our industry. In fact, he's wrong, based on your arguments, and on the fact that declared exceptions reduce the cognitive load on the programmer by eliminating the need to memorize all the return values of a given function. Not to mention, return codes never get checked anyway, so having the thread/process halt (and/or passing the offense up the call stack so a higher level error loop can handle it!) on an unchecked error is clearly the correct thing to do.
[gravatar]
Actually, you should have written :

STATUS DoSomething(int a, int b)
{
STATUS st;
st = DoThing1(a);
if (st != SGOOD) return st;
st = DoThing2(b);
if (st != SGOOD) return st;
return SGOOD;
}

...
STATUS st;
st = DoThing1(a);
if (st != SGOOD) cout
[gravatar]
Ouch... My comment was cut off :-(.

Actually, I wanted to point that you forgot the whole try/catch mess in your exception example...

Exceptions does not keep code as clean as you try to show.
[gravatar]
Which would I rather have: Invisible or visible problems?

The answer clearly depends on circumstances. Errors should be as visible as possible during debugging and testing - and invisible (if possible - and logged) when used by an end-user.

I think the general idea of exceptions is good but... To crash by default: Who did come up with such a bad idea?
[gravatar]
Andrew Eidsness 6:56 PM on 15 Oct 2003
I think that the point that's getting glossed over in this debate is the work required to undo side-effects. To take joel's suggestion (http://www.joelonsoftware.com/items/2003/10/15.html) and replace DoThing1 with CopyFiles, it becomes apparent that we can't just transitively throw an exception out of DoThing2; we also need to undo the work of DoThing1 e.g., un-copy the files. Otherwise our system will get into an unknown and therefore unrecoverable state (this would be even clearer if "CopyFiles" was "DeleteFiles").

In my opinion, the best discussion on exceptions starts with Tom Cargill's "Exception Handling: A False Sense of Security". When this article was first published (in '94) it was apparent to the C++ community that exceptions and their implications were not well understood.

The challenges he posed were eventually addressed on comp.lang.c++.moderated in the "Guru of the Week #8" thread (http://pierre.aubert.free.fr/divers/gotw/gotw008.htm). And I find Herb Sutter's _Exceptional_C++_ to be the best summary of the entire discussion.

My personal conclusion (I'm a fan of exceptions when they are properly used) is just a reinforcment of what should be standard programming technique; "functions should only do one thing".
[gravatar]
Here's Joel's "tricky" example done with exceptions:

void InstallSoftware(int a, int b)
{
FileCopier a;
RegistrySetup b;

// If we got this far without an exception
// then everything is ok
a.success();
b.success();
}

The idea is that a can undo anything it did, and will do so if you don't call a.success();

If any exceptions are thrown while the registry is being set up then a.success() isn't called and its destructor will remove all the files it installed.
[gravatar]
Ooops, I messed up the code, I didn't spot the input parameters:

void InstallSoftware(int a, int b)
{
FileCopier f(a);
RegistrySetup r(b);

// If we got this far without an exception
// then everything is ok
f.success();
r.success();
}
[gravatar]
First I find it ironic that Joel mentions goto in his comments against exceptions. I have run across examples of return value error handling code that use gotos to jump to a common segement of cleanup code at the end of a function to avoid redundancy.

Second, what bugs me is that there is little consistency in error reporting methods. Does it throw an exception? Does it return zero on error? Does it return non-zero on error? Does it return negative on error? Do I need to check a global runtime variable for the error code? Do I need to call another function to get the error code? Which function should I call? Does it use setjmp/longjmp? Does it throw a structured exception?

Third, exceptions have their own traps. Don't go letting your destructors propagate exceptions. You'll probably be surprised what throws clauses do in C++. Should you catch(E e), catch(E *e) or catch(E &e)? Are you checking new's for null? Time to break out the Meyers and Sutter books...

Of course, you could also talk about checked exceptions, another hotly debated topic...
[gravatar]
Some of the same points against status returns also apply to exceptions.

Both methods suck, frankly. At least Java makes an attempt to make exceptions better/safer.
[gravatar]
What a glaring omission in your original comparison (under "Clean Code") -- you do not show your exception handling! This tells me everything I need to know about the depth of your argument. I completely side with Joel on this one, exceptions are like gotos, for programmers who think they can avoid the issue of errors and send them all off in one corner.
[gravatar]
Don't forget about the performance implications, too. With status returns, you have code executing on every call (regardless of success or failure) to check the status code. With exceptions, the success case (which we hope is most of the cases) does not have that extra code.

Years ago at Lotus one of the architects did an actual benchmark of some code in 123 written first with the status technique and then with the exception technique, and claimed that the exception technique was 15% less code (he was comparing compiled code size) and maybe 5% faster.
[gravatar]
I happen to use c# and the .NET Framework.

I can predefine my own exception objects, store any pertinent information in the object that may help me debug the code, or find a way to handle the exception gracefull.

So I really have both...I can use exceptions and return usefull info on the exception.
[gravatar]
To Mr. 'Glaring omission':

Uh--I think you missed the point. The point is that the code with exceptions will do something on an error whether you catch the exception explicitly or not. (In this example, he doesn't, meaning it would probably be caught by the OS and the program would be shut down.) In the example with status codes, if you didn't check the return code, it will trundle merrily on its way, even if something went horribly wrong.

As to which approach is better, I'm not sure--both sides have valid points. I just wanted to point out that your comment ('This tells me everything I need to know about the depth of your argument') is by no means a deep argument in itself. All it demonstrates is that you either didn't understand or just didn't bother to really read the article.
[gravatar]
Bravo! Brilliant article. I've just spent a couple of hours exchanging blog comments with my friend who got too excited by Joel's article on exceptions, using most of the arguments you use. If I read this article before, I would just give him a link.
[gravatar]
I think this reason this gets cast as a religious argument is that fundamentally the 2 mechanisms do the same thing. Both require careful use, and both can be abused easily.

There typically are 10 kinds of people, those who think in binary and those who don't... oops, throw XInappropriateMetaphor.

Anyway, I think the religious disagreement is between people that prefer explicit handling of errors vs. those that prefer implicit handling. The reason I say this is that if you're in the explicit camp, exceptions are even more bloated than status returns (both in clutter and execution overhead), which removes a significant fraction of their advantages.

I tend to live in the explicit camp, personally. But that said, it's perfectly clear to me what problem exceptions are intended to solve.

But consider this: either a piece of code knows what should be done if a function it calls fails, or it doesn't. If it does, it can, and probably should, handle the error, to avoid too much "action at a distance". If it doesn't know what should be done, there's not much point in it checking for or handling the error. Passing the buck just introduces unnecessary implementation coupling.

Of course, that just recasts the debate into terms of whether it's better to crash or fail softly when an error occurs.
[gravatar]
Mathieu Routhier 9:35 AM on 22 Oct 2003
Here's just another suggestion about returning statuses, which I think is cleaner.

STATUS DoSomething(int a, int b)
{
STATUS st=SGOOD;

if (st == SGOOD)
st = DoThing1(a);

if (st == SGOOD)
st = DoThing2(b);

return st;
}

Obviously, you could omit the first check for status.
[gravatar]
Kyle Jedrusiak:

What two things do you mean when you say "I really have both"? Exceptions already come with richer information (there's a section in the article about this).
[gravatar]
ogd (somewhere between dog and god) 5:16 PM on 23 Oct 2003
I'd have to say hmmm... and yes. I'd have to say that Andrew @ the top of this discussion really nailed it.
Functions should only do one thing. If they don't then proceed at your own risk.
I don't know how much code I have reviewed (and developed in the past) that has not met this criteria to the detriment of readability and maintability; and of course reusability.
Even in Joel's modified trival example, it's copyfiles() that should be rolling back what it called... hopefully copyFile()
I use both statuses and throw exceptions depending on the situation. Especially on private functions I tend catch exceptions and return statuses, and then on public functions handle statuses and throw exceptions. The assumption is that I know how my privates are used, but obviously not the publics.
[gravatar]
First of all, exceptions and status do not solve the same problem. Under "Richer Error Information", Ned writes "perhaps the caller knows that it is OK for a file to be missing". So, you're going to throw an exception, huh? Any idea what the performance hit is for an exception? Here's a bit of code:

void ThrowExceptionFunc()
{
static int ex;
int i=0;
throw ex;
}

( loop 1 million times )
{
try
{
ThrowExceptionFunc();
}
catch( int ex )
{
}
}

Compiled with VS.NET, Release build, if you comment out "throw 1", this executes in 0.002 seconds. If, on the other hand, you throw an exception, this code takes over 4 seconds. Throwing exceptions is a performance hit.

In "More Effective C++", Scott Meyers writes "Compared to a normal function return, returning from a function by throwing an exception may be as much as three orders of magnitude slower. That's quite a hit... If, however, you've been thinking of using exceptions to indicate relatively common conditions... now would be an excellent time to think again."

So, exceptions should ONLY be thrown under exceptional conditions - not for "just thought you should know" messages.

I don't like status values either. I prefer the method of writing a small class which holds error information. You have to know something about performance to make it work efficiently, but you can impliment all kinds of great warning, error, and success messages which don't require the performance overhead of exceptions and are more useful than a status number.
[gravatar]
OK, one major point is being missed here: Exceptions help the problem of separation of concerns in a way that result codes can't. This is important in layered software: suppose at the bottom we have layer A; on top of it is layer B, and on top of that is layer C.

When an exception occurs in layer A it may only be of concern to layer C. For example, C may know how to handle it, while B can't. A solution without exceptions would mean the error condition will crosscut B's implementation. This is also a reason why RuntimeExceptions in Java are better, because you aren't forced to check the exception or declare it in the throws clause.

(The language extension AspectJ provides better support for exceptions and allows the "softening" of need-to-handle exceptions.)
[gravatar]
Grant: you should catch(E& e). Funny how some of Scott Meyer's items are still sticking to me after all these years of Java programming...
[gravatar]
Hmmm.. I don't think Joel's second argument is even valid. Returning on error from a function requires me to first identify that the error occured, and then do 'something' to return -

SetLastError();
return;

or

return x;

or

throw "Exception";

or some other mechanism of notifying the caller.

I still have an exit point wherever I would discontinue current function execution. Multiple exit points are just a prevalent in non-exceptional :) code as it is with exceptional code. Even with exceptions its possible to minimize exit points from any function with good design.

As to his first argument, I agree with Ned and one of the previous posters. If exceptions are clearly declared, meaning you are using a compiler that understands declaration of exceptions that can be thrown from any function, nothing is hidden from the programmer. I would say it is just the opposite. With clearly declared exceptions the programmer can easily see the complete list of all possible error conditions from a given function.
[gravatar]
Why is everybody bashing goto? Goto is almost required for error handling with status codes. For instance, if we do three things, createDirectories, installFiles, updateRegistry, we have multiple things to undo on failure:

int doStuff() {
if (!createDirectories)
goto err_create_directories;
if (!installFiles())
goto err_install_files;
if (!updateRegistry)
goto err_update_registry;
return SUCCESS;

err_update_registry:
undoUpdateRegistry();
err_install_files:
undoInstallFiles();
err_create_directories:
undoCreateDirectories();
return ERROR;
}

Otherwise, how do you undo all of those things?
[gravatar]
Exceptions are nice, but you lose "context" information. When the same method is called repeatedly from within one routine, how does the "catch" block in that routine determine which call caused the exception to be thrown?

With return status checking, you know where you are. The exception example should add the appropriate code to determine this - thereby making it as messy as the return-status-checking theory.
[gravatar]
This problem was solved a while ago in C++. See appendix e of the 3rd edition of Bjarne's book. All I can say is that I have seen the techniques work and that I personally do not find them at all onerous.

As for context, you can always do catch-add_context-rethrow. Works for me.
[gravatar]
Mikael Brockman 1:32 PM on 10 Nov 2003
It should be specified that this article deals with the merits of C++, not of return values and exceptions. Return values are great to work with in Haskell, when you understand and use monads. Of course, monads can be implemented in many other languages, but using them in C++ would be very tedious.
[gravatar]
I'm not sure why people keep bringing up performance hits with regards to exceptions.

Yes, exceptions have a performance hit, and that's not necessarily a bad thing. If you have a function that will fail regularly enough to cause a performance hit with exceptions, then it's likely what's causing it to fail isn't very "exceptional", and status codes should be used.

Joel's points against exceptions sound rather well reasoned at first, but they aren't.

Invisible exceptions are no more problematic than invisible status codes. Just as you may not know the exceptions a function may use, you just as well may not know the convention for returning error codes, especially when the function is returning a useable value as well. It doesn't matter whether you use status codes or exceptions, neither avoids the necessity of proper documentation.

Joel's second point is almost moot in itself. As Ned explained, an exception that isn't immediately caught will propagate until it is, even if that happens to be at the OS level. If a function leaves data in an inconsistant state, then your destructors need work. In addition, the ability of exceptions to propagate between layers without introducing dependancies, mentioned by Macneil, is half the benefit of their use.


In response to Warren, yes, handling exceptions from reoccuring calls will introduce more code in situations where context is important. Even ignoring the probably necessary refactoring of said function, the additional code is no more than what you'd have with status checks, and retains the benefits of exceptions.


This isn't a religious issue, it's a technical one. One solution behaves one way, the other in a different manner. Research and evaluate the behaviour of each, and decide for yourself.
[gravatar]
First: I'm pro exception.
But it has it's own catch.

Try to create an exception object when the exception was "out of memory".

And try to solve Mike Mangino's goto/status example with exceptions.

Is there "one thing fits all"?
[gravatar]
> Try to create an exception object when the exception was "out of memory".

Pre-create the exception at the beginning of the program and throw it only if the event arises.

> And try to solve Mike Mangino's goto/status example with exceptions.

Something like this would work:

void doStuff() {
try {
createDirectories;
try {
installFiles;
try {
updateRegistry;
} catch (Exception e) {
undoUpdateRegistry;
throw e;
}
} catch (Exception e) {
undoInstallFiles;
throw e;
}
} catch (Exception e) {
undoCreateDirectories;
throw e;
}
}
[gravatar]
> Something like this would work: [...]

Nested try/catch? Is that better, than nesting "if"?
And when you have 5-6? Can you read/debug properly?
[gravatar]
> Nested try/catch? Is that better, than nesting "if"?

Is there anything wrong with nesting "if" statements and what has this got to do with exceptions?

> And when you have 5-6? Can you read/debug properly?

When the code is properly laid out (unlike in this web forum) you can easily see the paths of execution that might transpire.

But I think you're missing the point: yes, if you want to have several operations, each of which might fail and each of which requires clean-up if they do, you'll need a relatively complex construct. Ultimately, you should have coded the operations themselves to catch their errors and clean up so that they could be used elegantly. So you'd then have:

void InstallFiles() {
try {
// Whatever
} catch (Exception E) {
// Cleanup
}
} ... and so on, for each function, then you can just go:

InstallFiles;
UpdateRegistry;
CreateDirectories;

The benefits that exceptions provide even in this case remain numerous:

- Errors can be easily propagated to the level at which they are best dealt with

- Obscure status code values do not have to be memorised yet errors cannot be accidentally ignored

- You can write the code to deal only with specific exceptions that you might expect to happen (eg, FileNotFoundException) and yet it will still operate in the case of less-common exceptions such as MemoryFullException by passing the error on automatically

- Additional information can be tied to the Exception (eg, a FileNotFound exception can store a filename where a status code of E_FILE_NOT_FOUND cannot)

The transparency of exceptions is a benefit, not a problem. A great percentage of the lines of code in a program can fail and dealing with every single circumstance individually complicates the program significantly, making it harder to understand. It also has the effect of complicating testing to the point where it might become almost impossible.

For instance, imagine code which creates two new objects:

Object o = new Object;
Object p = new Object;

Both of these calls could fail: there might not be enough memory, the object's constructor might throw an exception, etc. If these objects are particularly liable to fail, armed with exceptions, a programmer can choose to catch the exceptions and deal with them specifically - but if the chance of the functions failing is extremely remote or there is little they can realistically do if they _do_ fail, they can ignore them at this level and allow them to be dealt with by a higher level handler.

Without exceptions, checking return values becomes complusory if reliability is to be maintained. The two simple lines above become:

Object o = malloc(sizeof(o));
if (!o)
; // Do what? Quit? That's useful..
Object p = malloc(sizeof(p));
If (!p)
; // Do something imaginative

Now imagine having to write a test module that arranges things such that the creation of Object o succeeds and Object p fails and you begin to see how explicitly dealing with all possible errors might actually decrease reliability of programs.

The argument that functions have multiple possible exit points with exceptions does not hold because in the majority of cases, functions which check return values will bail out in the case of errors anyway. Exceptions consolidate the error-handling code for all cases into "catch" handlers and in fact probably serve to decrease the number of code paths that could be taken.
[gravatar]
Just thought you might be interested in reading my latest post here. Mentioned your article.
[gravatar]
I arrived at your page by Googling: "C++" exceptions return status method comparison

Unfortunately, your site picked up that fact and highlighted in different colored backgrounds the seached for words -- making your page irritating to read at best. DON'T DO THAT!
[gravatar]
I think there are other ways of checking for errors besides using exceptions or error codes.
For example, some programmers prefer to use global state variables or functions.
( For an example, see OpenGL )

Moreover, some ( professional ) programmers don't even bother checking for errors.
( Hey it either works or it doesn't work. )
For an example of this type of coding,
see the quake 3 source code.
[gravatar]
This is really not that complex.

You use exceptions for exceptional situations. For example, out of memory rarely happens and the function allocating memory can rarely do something intelligent with it anyways, so exceptions are a win. On the other hand, lookup in a hash table can and will fail often. Much better to return a Maybe (Haskell), option (SML), past-the-end iterator (C++), null (Java), or similar instead.

Just like everything else that's a (holy) war, neither is always better. Use the one that fits better and you'll be better off than anyone that thinks one is always better.
[gravatar]
Count me as heretical -- I'm with Joel in hating exceptions.

Status codes are always going to clutter up your code with explicit error handling in a way that exceptions don't, but explicitly declaring the exit points in your code is much better than only knowing them implicitly.

As far as hijacking the return value, I don't. In C#, I return a struct: Result. You wrap the result object around your return value (if any), and it returns back all nice and strongly typed.

Result ri = obj.DoStuff();
if (ri.Error) { // propagate error }
int myValue = ri.ReturnValue;

Yes, int myValue = obj.DoStuff(); is certainly cleaner. But it's easy to miss pieces of state that need to be cleaned up if you don't handle error cases explicitly. Exceptions leave subtle time bombs that go off in your code and break the application state. Error handling gives you an explicit place where you need to clean up anything interesting that happened in your method before exiting.
[gravatar]
Update: your site doesn't like angle brackets.

That should be Result<T> and then

Result<ri> = obj.DoStuff();
[gravatar]
Sigh.

Or, rather,

Result<int> ri = obj.DoStuff();
[gravatar]
I almost always have 1 entry point to functions (obviously) and 1 exit point.

Exceptions allow for poor programmers to do really nasty things, it is similar to why most programming courses tell you never to use gotos, its not that gotos can't be used effectively, and same with exceptions, its just it requires a lot of thought and planning, and you making your own rules involving their use.

I do not like your first snippet, I would do it like this:

STATUS DoSomething(int a, int b)
{
STATUS st;
st = DoThing1(a);
if (st == SGOOD)
{
// only do thing 2 if thing1 is good
st = DoThing2(b);
}
return st;
}

If you were to add more "things" and needed to different things when different errors occured you'll find the status technique is a lot easier to follow.

From my experience it really depends on what I'm doing, in general status's make it harder to whip out code really fast, and error handling code is more spread out, but it does result in less buggy code. If I am writing short scripts to do work for me, exceptions are awesome, I love them! You hardly need to write error handling code because you will get a nice error message to the screen when it crashes, but if you are writing a big system that will cause a catastrophe if it crashes and doesn't have a user sitting there watching the screen then using exceptions can be really tricky, most people can't do it, and it will result in buggy code. Imagine the code that controls airplanes was written using exceptions, the thought of that scares me.
[gravatar]
>Why is everybody bashing goto? Goto is almost required for error handling with >status codes. For instance, if we do three things, createDirectories, installFiles, >updateRegistry, we have multiple things to undo on failure:

Absolutely not.
Create an enum and throw this enum, catch and do a switch( my_error_enum ), which by nature cascades through (unless on break), and cleanup in reverse order of the function calls.

I wouldn't hire anyone that used goto's, or returned error values. Ever.
[gravatar]
Hi,

i'm with Joel.

the sample should be more like:
STATUS DoSomething(int a, int b)
{
STATUS retVal = DoThing1(a);

if (retVal == SGOOD)
retVal = DoThing2(b);

return retVal;
}

This is absolutely fine, especially if someone else wants to
change or extend it in the future, because it is
obvious what to do e.g.:

STATUS DoSomething(int a, int b)
{
STATUS retVal = DoThing1(a);

//this works always!!!
DoSomethingImportant(a);

if (retVal == SGOOD)
retVal = DoThing2(b);

return retVal;
}

that was easy, wasn't it?

Now lets have a look at Exception base code:

void DoSomething(int a, int b)
{
DoThing1(a);
//this does not!!! works always
DoSomethingImportant(a);
DoThing2(b);
}

In a big Project it is absolutely standard that different people manage the same code within several years.
This has nothing to do with skillsets... but just only giving you the possibility to manage and extend
your code fast without checking the hole thing over and over again for a small change. like that.

To make an extension like that in the sample above with exception based code you will have to rethink the whole
function and why and how the functions depend on each other.

However the problem is not only about redesigning the exception based functions here to make it work, the bigger
problem is, that the person adding DoSomethingImportant(a); does not know about the other stuff and
introduces a bug that happens only sometimes depending on something totally different.
This just gets worse when you span the sample above over a hierarchy of methods.
[gravatar]
I am with Ned :-)

The "DoSomethingImportant" should properly be put into a finally block.

Throwing an exception should be considered in situations where the function/code cannot complete the job it is designed for.

Return values should be used for returning data to the caller.
[gravatar]
You are not helping by highlighting google search terms, only making it more annoying until I reload the page. If I wanted the terms highlighted I would have viewed the "cached" link. I don't get why people add this crap feature to their blogs.
[gravatar]
@Mikael

i think it is very difficult to define generally what is meant with:
"function/code cannot complete the job it is designed for"

The mentioned STATUS return value might be a Result (a custom class) containing status and return value, which btw is a pattern that works also between process/layer boundaries.
[gravatar]
Thanks for the guide. I think I will start using exeptions in my new project!
[gravatar]
I think that both are pretty much the same in terms of usefulness:

With exceptions:
*You can check them.
*If the function fails and you don't check them your program will crash and say something that the user will not understand anyway.

With error numbers and returns:
*You can check them.
*If the function fails and you don't check them your program will either:
*crash and say something that the user will not understand anyway (likely)
*crash silently (also likely)
*complete execution successfully or semi-successfully
*seriously and actively mess up something
[gravatar]
@Josh W, your "solution" is crap. It's more complicated, slow, and very hard to read. Don't just bash goto because you found people bashing it. There are times when goto is the right choice. Deal with it! And for your information, I wouldn't work for an employer like you too!
[gravatar]
Your argument basicly boils down to the facts that your favourite compiler cannot return status info very well, and that exceptions will work somehow even if programmers are lazy. Joel Spolsky has thought one step further and says that programs by such programmers will be full of bugs, and these bugs are of a kind that are hard to find. I must say that based on my experience I fully agree with Joel. Hiding info from the programmers is a very bad idea. If the programmers don't care which library functions can fail when and how, then the program will be garbage. Also on one hand assuming that the programmers throw all required exceptions in their own code, but on the other hand ignore errors occuring in called code, is a contradiction in itself. A good programmer will do both, a bad programmer will do neither, and you want such a programmer to neither write own functions, nor write code that uses other libraries, regardless of how errors are propagated back.
[gravatar]
Thank you for an interesting essay, Ned.  As one who
learned programming and wrote his first programs  in
Pascal  and  Modula,  both  procedural  languages, I
graviate towards Joel's opinion.

> Exceptions let you leave error handling  code  out
> of much of your code.

Or, to be more specific, out of the middle layers of
the rainforest.  But this is not always true.  I of-
ten  find it useful to add new information to my er-
ror messages at several levels of  the  call  stack,
e.g.:

   Error synchronizing repository:
     Failed to synchronize the file 'foo':
       Failed to delete the local copy of 'foo':
         Access denied.

How  does  one effect this with exceptions if not by
catching and rethrowing them  whenever  new  details
become  known?   And  mind  you  that  catching  and
rethrowing produces way more clutter than the proce-
dural approach:

   bool DoThingA( int a, out string err_msg )
   {  const string ERR_PREF = "Error doing A: ";
      bool ok = false;
      err_msg = "";
      if( !DoA1( a, out err_msg ) )
      {  goto Error;  }
      if( !DoA2( a, out err_msg ) )
      {  goto Error;  }
      ok = true;
   Error:
      return ok;
   }

It  grows worse if the functions cannot report their
own context because the error interpretation depends
on the caller:

bool DoThingA( int a, out string err_msg )
{  const string ERR_A1 = "Error doing A1: {0}";
   const string ERR_A1 = "Error doing A2 with {0}: {1}";
   bool ok = false;
   err_msg = "";
   if( !DoA1( a, out a1, out err_msg ) )
   {  err_msg = string.Format( ERR_A1, out err_msg );
      goto Error;
   }
   if( !DoA2( a1, out err_msg ) )
   {  err_msg = string.Format( ERR_A2, ai, out err_msg );
      goto Error;
   }
   ok = true;
Error:
   return  ok;

Should you propose to surround each nested call with
an individual try..catch block?  I hope not!

> If the code were more complex than this, the extra
> noise from the error handling would be much worse.

Error handling is not noise but an essential part of
a progam.  Removing it into a parrallel control flow
tears  the  algorithm in two across a thick, tightly
coupled cross-section.  The  output  of  a  function
should  be processed at one point, prefereably right
after its invocation, and error information certain-
ly belongs to its output.

> Exceptions  are  transmitted automatically through
> layers that have no knowledge of them, so you  can
> write useful code that has no error handling logic
> at all.  This helps keep the code  straightforward
> and usable.

Exceptions  do not make code any more usable than if
it had explicit error handling, but they do,  howev-
er,  make  it look more straightforward, but that at
the expense of detailed error messages, for whenever
you neglect to put a try..catch you lose a chance to
make a specific human-readable message  telling  ex-
actly which operation failed.

> With  status returns, a valuable channel of commu-
> nication (the return value of  the  function)  has
> been  taken  over for error handling. Some methods
> are so simple, and conceptually return a value, so
> human  temptation  takes  over,  and the method is
> written to return the value rather than  a  status
> code. "It's a simple function, it can't fail, this
> will be more  convenient".  Over  time,  the  code
> grows,  and  the  method gets larger, calling more
> helper functions, and pretty soon it can fail, but
> it has no way to express it.

True,  but  the  experienced  developer will in most
cases anticipate it and make  the  necessary  provi-
sions beforehand.

> Status  returns  are  typically an integer.  A few
> bits are reserved for flags (for example, a sever-
> ity  indication),  and the rest are a large set of
> enumerated failures. This is a fairly impoverished
> error  value.  For example, suppose the failure is
> that a file could not be found. Which file? A sta-
> tus return can't convey that much information.

Exceptions are not the only soluion.  One can always
return whatever error-realted information one  shall
see  fit.  You wrote it would result in many ad-hock
protocols or convensions, but wrapping  it  all   in
arbitrary  descendents of Exception is hardly better
and looks more like sweeping the garbage  under  the
rug.

> Exceptions  are  instances of classes, and as such
> can carry as much information as they need to  ac-
> complish  their  task.  Because  they  can be sub-
> classed, different exceptions can carry  different
> data,  allowing  for  a very rich zoology of error
> messages.

If one follow the general recommendation  to  handle
errros  as early as possible one will hardly need to
pass so much information up the stack.  In the  rare
cases when I need this, other techniques are usually
more convenient, such as returning a list  of  files
that  a  subroutine has failed to synchronize.  Even
if you put it inside an exception  all  the  invoca-
tions  will  still need to be modifed so as to avail
themselves of this new information.

> As an example of the richness exceptions  can  ex-
> press,  Java  defines  exceptions  as containing a
> reference to another exception, so that chains  of
> effect  can be constructed and retained as the ex-
> ception moves up through layers of handling.  This
> allows  for rich diagnostic information: exception
> B occurred here, and was caused by exception A oc-
> curring there.

This  requires that the programmer handle exceptions
at several layers, defeating  your  own  concept  of
clean medium-layer code.

> Status  returns can't even be used with some func-
> tions.  For example, constructors  don't  have  an
> explicit  return type, and so cannot return a sta-
> tus code.

Factory methods are at your service.

> Destructors may not  even  be  explicitly  called,
> never mind that they don't have a return value.

It  is  a problem with all OO languages that support
exceptions: their designers are forcing  programmers
to  employ  one  specific  error-handling mechanism.
Use procedural code, where this can never happen, or
make an exemption for exceptions in such cases.  But
unhandled exceptions in destructors seem a bar idea,
anyway.

> When  a status return goes unchecked, a failure in
> the called routine will be  undetected.  The  code
> will  continue  executing as if that operation had
> succeeded.  There's no way  to  characterize  what
> might  happen  at that point, but it is clear that
> no one will know that an error had occurred.  Per-
> haps the code will visibly fail later on, but that
> could be many operations later. How will you trace
> the problem back to the original failure?

It  may happen, but rarely does in my practice.  If,
for example, an error during opening a  file  stream
were  unhandled  the following write operation would
fail  with  a  reasonalbe  error  message,  such  as
"stream  is not initialized," so it is not difficult
to track down the occasional unhandled error, where-
as a little self-discipline will help avoid such er-
ros in the first place.

About Joel's condemnation of exceptions you wrote:

> This seems like a reasonable  argument  until  you
> work out what the code would look like with status
> returns.

The correctness of his reasoning against  exceptions
cannot depend on the appearance of code with explic-
it error handling, can it?

> We aren't arguing here whether functions should be
> able  to fail or not, just what should happen when
> they do. So all of those possible exit points  for
> a function are still possible exit points, but you
> have to check the status returns  explicitly,  and
> return from the function.

Not necessarily.  Instead of leaving the function by
an additional exit point, one can jump to  a  deini-
tialization  (clean-up)  section  and then egress by
the canonical single exit:

   if( !CreateStream(..., out stream, out err_msg ) )
   {  goto ERR_CREATE;  }
   if( !OpenStream( stream, out err_msg ) )
   {  goto ERR_OPEN;  }
   if( !WriteData( stream, ..., out err_msg ) )
   {  goto ERR_WRITE;  }
   ok = true;
ERR_WRITE:
   CloseStream( stream );
ERR_OPEN:
   DisposeStream( stream );
ERR_CREATE:
   return ok;

Note that implementing the same logic via exceptions
would require nested try..catch..finally blocks, and
it is rather deplorable that adding  another  opera-
tion to a linear algorithm should necessiate another
level of nesting!

You also ignored Joel's notion that  exceptions  are
invisible  goto's sprinckled in the code.  Moreover,
they are vague: 'throw' means "go  somewhither"  and
'catch'  "come  somewhence" -- the specific destina-
tion and source unknown.
[gravatar]
@Anton: this is quite a rebuttal! Perhaps you need your own blog. :)

I won't reiterate all the points I've already made. I will point out that you talk about adding information as the error makes its way up through the callers, but then you show an example that doesn't do that. The kind of annotated error handling that you are advocating is very difficult to do with status returns, and common with exception handling.
[gravatar]
Ned Batchelder:

> this  is  quite a rebuttal!  Perhaps you need your
> own blog. :)

Yes, I might post it, although not in a blog but  in
my  projected  homepage, and, hopefully, in more re-
fined form, thanks to you.

> I won't reiterate  all  the  points  I've  already
> made.

Did I misunderstand them so badly as to require full
reiteration?

> I will point out that you talk about adding infor-
> mation  as  the error makes its way up through the
> callers, but then you show an example that doesn't
> do that.

My  first  exmple  shows a method of recording error
context when the interpretation of an error does not
depend  on the caller.  Every specific error text is
therefore prefixed with the same  extra  information
stored in the function itself.

My  second example shows a method of recording error
context when the interpretaion of an error may  dif-
fer  among  callers: each caller prepends the neces-
sary context to every error message it receives from
a nested call.

My  third  example  has no special handling of error
messages because it demostrates  another  aspect  of
the  dilemma  in  question, viz. the handling of se-
quential   initialization   and    deinitialization.
Whereas  the excepions model requires a new level of
nesting for each init-deinit pair, the GOTO approach
keeps  the  code  linear, which in this case is good
because the algorithm itself is linear:

   init1, init2, init3, deinit3, deinit2, deinit1.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.