Joel on Exceptions
“People have asked why I don't like programming with exceptions. In both Java and C++, my policy is:
- Never throw an exception of my own
- Always catch any possible exception that might be thrown by a library I'm using on the same line as it is thrown and deal with it immediately.
The reasoning is that I consider exceptions to be no better than "goto's", considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto's...“ [1]
Joel mentions two reasons for this rediculous view:
- 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.
- 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. “
Lets deal with each of these individually.
1. They are invisible to the source code: Yah, you find which exceptions might be thrown the same exact way you find which errors would be returned if you didn't have exceptions, RTFM. I don't see how it is really different in that respect... except that if I have multiple libraries, I am always gaurenteed that the base exception will be derived from the exception class. So, at the very least, this gaurentees that if I haven't written error handling code to deal with some new error introduced in an update to some other portion of the code (or one that I never bothered to deal with), I can implement some generic error handling in a consistant manner. If you are dealing with C++ libraries, this isn't the case. In one lib, you might have to check against anything other than S_OK, and in another lib, the clear state might be something like NO_ERROR. There is no required consistancy (in fact, you don't even have to return integers if you really don't want to). Now... although I don't like them, checked exceptions also solve this problem by requiring you do document all possible exceptions. So, for solving this problem, they are far superior to error codes, but they can add a lot of complexity to your app.
2. They create too many possible exit points for a function: Not really. This is why we have the “finally” clause. As long as you use the finally clause, you don't have to worry about things ending up in partial states, because the code in the block will always execute before the method returns. Of course, C# also gives us the using statement, which garentees that an object will be disposed without having to do anything explicitly.... but that is another topic for another day.
Joel's solution:
“a better alternative is to have your functions return error values when things go wrong, and to deal with these explicitly, no matter how verbose it might be. It is true that what should be a simple 3 line program often blossoms to 48 lines when you put in good error checking, but that's life, and papering it over with exceptions does not make your program more robust“
Really... lets take a look at the two approaches side by side.
Error Codes:
DWORD DrawFloodFilledBitmap()
{
DWORD error = NO_ERROR;
HBITMAP hBmp = CreateBitmap(width, height, planes, bpp, NULL);
if(hBmp == ERROR_INVALID_BITMAP)
{
// handle error
error = GENERIC_ERROR
}
else
{
// do some more stuff
BOOL success = FloodFill(hdcBitmap, x, y, color, fillType);
if(!success)
{
DWORD errorCode = GetLastError();
// handle error
error = GENERIC_ERROR;
}
DestroyBitmap(hBmp)
}
return error;
}
Exceptions:
void DrawFloodFilledBitmap()
{
Bitmap *b = null;
try
{
b = new Bitmap(width, height);
// do something
b.FloodFill(x,y,color, fillType); // yah, I know this isn't a real method
}
catch(Exception ex)
{
// handle errors
GenericException ge = new GenericException(“Could not draw bitmap“);
ge.InnerException = ex;
throw ge;
}
finally
{
if(b != null) b.Dispose();
}
}
Now, both contain relatively the same amount of code, but which is better? One thing you will note is that in the top method, we don't have a good way of returning one of our own, documented error messages, while still being able to provide more context (as in, yes, if failed... but, why?). We could return the individual error codes instead, but then we are back again to not having all our return values documented, which can make things messy for the handling code. Now, what happens if CreateBitmap is updated and now has two error values? Our code breaks, crashes and burns... and because of the way the method was defined, there isn't anything we can do about it either. We are left at the mercy of the library developers. However, by using exceptions, we have a consistant way to not only provide that deeper context (which is extremely useful when you just can't figure out why a method is failing), but we also have a consistant way to deal with new errors. Additionally, all our error handling logic is isolated in one portion of our code, which makes matinence a lot easier (ie. you don't have to dig around to find out where exactly the error is coming from).
Things get even more nightmareish when you start dealing with errors from different libraries. If I have a Win32 error, I call GetLastError to get extended error information and then from there, I can get a text based description of the error, but in library “x“, I might not have any way to get either extended information or a text based description of the exception. Both are completely up to the library developer. But, even if they do decide to implement these functions to help me out, now I have to call a different method for errors from each library I am calling into. Accidently call the wrong one, and boom...things blow up again, or they don't and you get some really screwy error messages.
Of course, pages upon pages could be written about the problems with the error code method, but eventually we will come to realize why most developers end up just ignoring error codes all together, it is just to cumbersome to deal with them properly. Yes, it can be done... but that doesn't mean it will get done.
[1] Exceptions. Joel Spolsky. http://www.joelonsoftware.com/items/2003/10/13.html
PS: looks like I'm not the only one who disagrees: http://discuss.fogcreek.com/joelonsoftware/default.asp?cmd=show&ixPost=77463&ixReplies=20