Exceptions are Awesome

Welcome to the year 2015. Obamacare is the law of the land, gay folks can get married, C++ has lambdas, and (this will surprise some folks) exceptions are the correct way to report errors. The fact that I still have to argue this point is, frankly, shocking. I don’t care that the Google style guide doesn’t allow them, and for the love of God don’t tell me they’re inefficient. Simply put, they are the way to report errors, you must know how to use them.

Now to some extent I actually sort of sympathize on this one. When I learned C++ (back in 1995 or so) exceptions weren’t widely used. And even up until around 2005 I was pretty convinced that they were a bad idea. But I’m old and that was 10 years ago… what’s your excuse? Some programmers today seem to have the same misconception I had in 2005, so let’s walk through the faulty logic. They argue that using exceptions makes code bloat substantially. They start with something like this:

return_code = blah();
if (return_code != SUCCESS) {
    return return_code;
}
return_code = something_else();
if (return_code != SUCCESS) {
    return return_code;
}

And apply exceptions by turning it into this:

try {
    blah();
}
catch (const std::exception& e) {
    throw runtime_error( "calling blah failed" );
}
try {
    something_else();
}
catch (const std::exception& e) {
    throw runtime_error( "calling something_else failed" );
}

And then argue that using exceptions has turned an 8 line function into a 12 line one. But they’re missing the point. So I’ll write down the point in bold letters: throw an exception when an error occurs, and catch an exception when you can do something about it. Armed with this knowledge, most junior programmers will head back to their keyboards and come back to me with something like this:

try {
    blah();
    something_else();
}
catch (const std::exception& e) {
    throw runtime_error( "calling blah or something_else failed" );
}

Well now we’re down to just 7 lines, so this is a step in the right direction, but it’s still wrong. Let’s take a look at the correct answer and then discuss why it’s correct

    blah();
    something_else();

Ah, much better. Now we’re just down to 2 lines, and better yet, we don’t have to worry about errors at all. We have two short, succinct lines that just assume the best case scenario and ignore errors completely, what could be better? “But wait, this can’t be correct, there’s no error handling at all!!!” And that’s the point. Take a look at the second part of the bold statement above, catch an exception when you can do something about it. In all of these examples there was nothing sensible we could do in the case of an error but just return an error up to the caller. Since an exception just naturally bubbles up the call stack, and since there’s nothing I can do about it here, just let it bubble up the call stack.

Do you see the beauty in that? Are you reading this and having a warm and tingly sensation rubbing your chest? If not, think about it some more. You are now free to write code where you don’t have to nit pick every single error case. By way of example, let’s say you’re writing some kind of web server. A request comes in for a web page and in order to respond you’ve got to hit a database for some content, the disk for some assets, and then maybe execute some business logic to tie it all together (God help you if it’s Ruby on Rails). Somewhere deep down in the bowels of your db connection something might go wrong. Do you really want to have to watch error codes at every function call and have to keep translating them as they work their way up the stack through multiple layers of return codes? Of course not, none of the intermediate layers can do anything anyway, the db is dead, they can’t fix that. Imagine a world where the db fails and the highest layer is simply notified so it can return a bland 500 error to the client. That’s the joy of exceptions. The db layer throws an exception, every layer in between ignores it, and the top layer catches it and returns. But wait, it gets even better. The system unwinds the stack for you, correctly destroying all the objects you created along the way. Sound awesome? That’s because it is awesome.

Oh but wait, isn’t it expensive? Well yes, it is, but that doesn’t matter. Why doesn’t it matter? Because exceptions are for exceptional cases. Take the case of our http server above. How often do you think a database blows up? Okay okay, I get the joke, almost constantly, but seriously, in the normal day of operations, how often? It’s an unusual thing, so who cares if it runs just a tiny bit slower? Users aren’t gonna be sitting on their web browser complaining that their 500 error page took an extra picosecond to return. In the normal run of things, exceptions don’t happen, so you don’t pay a performance penalty.

Of course this is only true if you don’t abuse exceptions. You need to make sure that you’re not using exceptions to report back something that is expected behavior. Let’s say you’re writing some function bool lookup_something_in_db( int id, Thing& thing_copy ). This function looks up a Thing in the db with the given id. It seems to me that the thing in the db might not exist. Or to say it in a more obvious way, it’s not an exceptional case that the id doesn’t find something in the db. So using an exception to return information that the thing doesn’t exist would be a really bad idea, you’d be throwing exceptions for cases that are expected. In this case returning a bool to notify the caller that thing they were looking for doesn’t exist is a good idea.

So here’s the bullet points:

  • Exceptions are awesome, use them
  • Throw when an error happens, catch when you can do something about it
  • Don’t use exceptions for expected, normal processing

And further reading

Leave a Reply

Your email address will not be published. Required fields are marked *