49

Possible Duplicate:
Defensive Programming vs Exception Handling?
if/else statements or exceptions

I often come across heated blog posts where the author uses the argument: "exceptions vs explicit error checking" to advocate his/her preferred language over some other language. The general consensus seems to be that languages that make use of exceptions are inherently better / cleaner than languages which rely heavily on error checking through explicit function calls.

Is the use of exceptions considered better programming practice than explicit error checking, and if so, why?

5
  • 4
    Mandatory Raymond Chen link: blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx
    – peterchen
    Sep 25, 2012 at 6:34
  • 5
    Return-value–based error checking need not be explicit; monads can be a syntactically inexpensive way to model all kinds of checked error handling. And I do advocate checked exceptions—if you look at an exception as a dynamically bound nonlocal return, you see how “swallowing” one is a pretty nonsensical thing to do.
    – Jon Purdy
    Sep 25, 2012 at 7:27
  • 1
    and a bunch more from the Related list on the right, including The modern way to perform error handling… Sep 25, 2012 at 7:53
  • @peterchen I didn't understand the example provided in the end of that link. What difference does it make whether the icon is visible or not? The (incomplete) object is not returned either way. Sep 25, 2012 at 14:35
  • Way to start a holy war :).
    – MrFox
    Sep 25, 2012 at 14:41

10 Answers 10

66

In my mind, the biggest argument is the difference in what happens when the programmer makes an error. Forgetting to handle an error is a very common and easy mistake to make.

If you return error codes, it is possible to silently ignore an error. For example, if malloc fails, it returns NULL and sets the global errno. So correct code should do

void* myptr = malloc(1024);
if (myptr == NULL) {
    perror("malloc");
    exit(1);
}
doSomethingWith(myptr);

But it is very easy and expedient to instead only write:

void* myptr = malloc(1024);
doSomethingWith(myptr);

which will unexpectedly pass NULL into your other procedure and likely discard the errno which was carefully set. There is nothing visibly wrong with the code to indicate that this is possible.

In a language that uses exceptions, instead you would write

MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);

In this (Java) example, the new operator either returns a valid initialized object or throws OutOfMemoryError. If a programmer must handle this, they can catch it. In the usual (and conveniently, also the lazy) case where it is a fatal error, the exception propagation terminates the program in a relatively clean and explicit manner.

That is one reason why exceptions, when properly used, can make writing clear and safe code much easier. This pattern applies to many, many things which can go wrong, not just allocating memory.

10
  • 11
    The last paragraph is most important - "..when properly used, can ..."
    – mattnz
    Sep 25, 2012 at 1:11
  • 3
    In addition to forgetting to check error codes, being too lazy to bother checking error codes is something I'm sure many of us are guilty of from time to time. "It won't happen to me" Sep 25, 2012 at 1:21
  • 11
    Another nice thing about exceptions is that they are typed and can contain additional information. Beats the hell out of juggling meaningless return codes like -1, 1, 0, 2 or whatever. Having defined names for these return codes helps, but that's putting lipstick on pigs.
    – Supr
    Sep 25, 2012 at 7:37
  • 5
    @FredOverflow: I'm going to be amazed if you can come up with a single error handling that applies to all situations where you ever allocate anything. In general, all your checked_malloc could do is pass the error on just like malloc does already.
    – Joren
    Sep 25, 2012 at 9:56
  • 3
    I meant just wrapping the code you wrote: void * checked_malloc(size_t nbytes) { void * p = malloc(nbytes); if (p) return p; perror("malloc"); exit(1); } Sep 25, 2012 at 9:58
60

While Steven's answer provides a good explanation, there is another point which I find is rather important. Sometimes when you check an error code, you cannot handle the failure case immediately. You have to propagate the error explicitly through the call stack. When you refactor a big function, you may have to add all the error-checking boilerplate code to your sub function.

With exceptions, you only have to take care of your main flow. If a piece of code throws some InvalidOperationError, you can still move that code to a sub function and the error management logic will be maintained.

So exceptions allows you to refactor faster and to avoid boiler plate.

6
  • 3
    In fact even when you catch an exception, very often you'll do some cleanup and then re-throw that exception (or throw one that should make more sense to the caller, translating to reflect the particular level of abstraction).
    – user8709
    Sep 25, 2012 at 6:01
  • +1, I completely agree. Without the ability to traverse up the stack in case of an error (with almost no boilerplate code), short methods would not be a favoured way of working.
    – Daniel B
    Sep 25, 2012 at 6:28
  • 3
    How do Java's checked exceptions play into this? Sep 25, 2012 at 9:30
  • 2
    @FredOverflow I have never used Java, but I have the feeling that I wouldn't like this feature. Sep 25, 2012 at 9:41
  • 2
    @FredOverflow: C++11 dropped exception specifications, because they were too big a hassle. And those were less cumbersome than Java checked exceptions to start with. (Didn't interfere with templates, for instance).
    – MSalters
    Sep 25, 2012 at 10:13
19

A point of view from a different angle: Error handling is all about security. An unchecked error breaks all assumptions and preconditions the following code was based on. This may open a lot of external attack vectors: From a simple DoS over unauthorized data access to data corruption and complete system infiltration.

Sure, it depends on the specific application, but when assumptions and preconditions are broken, then all bets are off. Within a complex software, you simply can't say anymore with certainty what is possible from then on, and what can and can't be used from extern.

Given that, there is a fundamental observation: When security is treated as an optional add-on that can be attached later, then it fails most often. It works best when it was already considered in the very first basic design stages and built-in right from the start.

This is what you basically get with exceptions: An already built-in error handling infrastructure that is active even if you don't care. With explicit testing, you have to build it yourself. You have to build it as a very first step. Just beginning to write functions that return error codes, without thinking about the big picture until you're in the final stages of your application development, is effectively an add-on error handling, doomed to fail.

Not that a built-in system helps in any way. Most programmers don't know how to do error handling. It's just too complex most of the time.

  • There are so many errors that can happen, and each error requires its own handling and its own actions and reactions.

  • Even the same error can require different actions, based on context. What about a File Not Found or an Out Of Memory?

    • FNF - A third party library needed for your app to run? Terminate.
    • FNF - The start-up config file of your application? Start with default settings.
    • FNF - A data file on a network share the user wants your app to open? Disappearing files can happen anytime, even in the microseconds between Exists() and Open(). It's not even an "Exception" in the literal meaning of the word.
    • OOM - For a 2GB allocation? No surprise.
    • OOM - For a 100 byte allocation? You're in serious trouble.
  • Error handling leaks through all your carefully separated abstraction layers. An error in the lowest level may require to notify the user with a GUI message. It may require a decision from the user for what to do now. It may need logging. It may need recovery operations in another part, e.g. open database or network connections. Etc.

  • What about state? When you call a method on an object that invokes state changes and it throws an error:

    • Is the object in an inconsistent state and needs a rebuild?
    • Is the object state consistent but (partially) changed and needs an additional rollback or a rebuild?
    • Is a rollback done within the object and it remains unchanged to the caller?
    • Where is it even sensible to do what?

Just show me one learner's book where error handling is rigorously designed from the start and consequently used through all the examples, without being left out for brevity and readability and as excercise for the reader. If this is applicable from an educational POV is another question, but it's no surprise that error handling is often enough a second or third thought when it should be the very first.

12
  • "FNF - The start-up config file of your application? Start with default settings.": The file not found exception should be caught immediately by the code opening the file (and the exception can be avoided by an if (file.exists())). Implementing the normal flow of control through exceptions is not a good practice IMO.
    – Giorgio
    Nov 16, 2012 at 20:41
  • @Giorgio: With explicit error testing there are no "exceptional" code paths. Everything is normal code flow. The decision if a condition is checked or if the error just happens is in fact an additional complexity of exception handling. The decision of what to do about it has to be made in both cases. And the config file could as well disappear between Exists() and Open(). So you better catch this in any case.
    – Secure
    Nov 16, 2012 at 23:02
  • 1
    Yes, but you do not intentionally try to open a file without checking and use try / catch to implement what logically is an if then else. Exceptions are there to handle exceptional cases / errors, not situations that are perfectly acceptable.
    – Giorgio
    Nov 17, 2012 at 0:01
  • 1
    I'm with you here, and I didn't say otherwise. But isn't a missing file that is required to run the program or that was selected by the user as input an exceptional situation? Shouldn't it trigger the same reaction, regardless if the error catching occurs before or after the fact? And how are you going to report the failure of exists() to the caller? At least I hope you don't put all the reaction stuff including the GUI code into this one method, but have a clean separation of concerns...
    – Secure
    Nov 17, 2012 at 9:02
  • 2
    This is a possibility, but it is dangerous. A file can "disappear" at any moment, e.g. when it is on a network share with an unstable network connection. It can disappear before exists(), between exists() and open(), or in the middle of the read operation. After the read, the file may be corrupted. It may contain lines in invalid format. It may fail a checksum test. It's not done with a simple exists(). Shouldn't all these cases lead to the same reaction? Ask the user "Config file not found or unreadable. Load another file or start with default config?"
    – Secure
    Nov 17, 2012 at 10:21
16

I'd like to think of exceptions as...

They allow you to write code the way it naturally tends to be written

When writing code, there's a number of things that the human brain needs to juggle:

  • What is the logic I am trying to express here?
  • How do I actually accomplish this in language XXXX?
  • How does this code fit into the rest of the application?
  • Why do I keep getting these build errors?
  • Am I forgetting about boundary conditions?
  • Is this code readable and maintainable enough?
  • What happens if something in this code fails?
  • Am I producing code in consistent style and use the right coding conventions?

Each one of these bullets requires a certain amount of concentration and concentration is a limited resource. This is why people who are new to a project will often forget things towards the bottom of the list. They are not bad people, but because so many things may be new to them (language, project, weird errors, etc...), they simply do not have the capacity to deal with other bullets.

I've done my share of code reviews and this trend is very apparent. A person with less experience will forget more things. These things include style and often enough error handling. When people are having a hard time getting the code to work, error handling tends to be in the back of their minds. Sometimes they'll go back and add some if-statements here and there, but rest assured, they will forget a whole bunch of things.

Exception handling allows you to write code where the meat of your logic is written as though there are no errors. You make a function call and the next line can simply assume the previous line succeeded. This has an added bonus of producing code that is much easier to read. Have you ever seen an API reference with code examples that had a comment, "// Error handling omitted for clarity"? If they remove error handling in documentation to make the example easier to read, wouldn't it be great if you could do the same thing in production code and make that easier to read as well? That's exactly what exceptions let you do.

Now at least one person reading this post will say, "pffttt some noob doesn't know how to code. I never forget error handling." a) Good for you, but more importantly b) as I said above, coding requires your brain to concentrate and there's only so much brain to go around; that applies to EVERYONE. If your code is easy to read, that's less concentration. If you can put try/catch around a large chunk and then simply code application logic, that's less concentration. Now you can use that extra capacity for more important things, like getting the actual job done that much faster.

6

Advantages of exception throwing over error code returning:

  • The return value of a function can be used for what it was designed to do: return the result of the function call, not a code that represents success or one of many failures within the function. This creates cleaner, more elegant code; fewer output/reference parameters, assignments are made to things you actually care about and not to throwaway status codes, etc.
  • Exceptions are object-oriented, so exception types have reusable conceptual meaning. What does a return code of -2 mean? You have to look it up in the documentation (which had better be good or you're forced to trace code, and if you don't have source code you're really screwed). What does a NullPointerException mean? That you tried to call a method on a variable that doesn't reference an instance. In addition, exceptions encapsulate data, so they can tell you exactly how and where your program screwed up in easily human-readable means. A return code can only tell you the method screwed up.
  • By default, return codes are ignored; exceptions aren't. If you don't store and check the return code, your program merrily continues along, corrupting data, screwing up pointers and generally crumpling itself into garbage. If you don't catch an exception, it's thrown out to the OS which terminates your program. "Fail fast".
  • Exceptions are more easily standardized. Because exceptions create more self-documenting code, and because most of us don't write all our programs 100% from scratch, exception types covering a wide variety of general cases are already available. The cheapest solution is the one you already have, so these built-in exception types get used in situations similar to the ones they were originally created for. Now, an InvalidOperationException means you tried to do something inconsistent with the current state of an object (exactly what that was will be detailed in the error message), regardless of which function from which third-party library you were trying to call.

    Contrast that with return codes; for one method, -1 might be a "null pointer error", while -2 might be an "invalid operation error". In the next method, the "invalid operation" condition was checked first and so that error got return code -1, while "null pointer" got -2. A number's a number, and you are free to create any standard for assigning numbers to errors, and so is everyone else, leading to a LOT of competing standards.

  • Exceptions allow you to be more optimistic. Instead of pessimistically checking everything that could go wrong with a statement before actually executing the statement, you can simply try executing the statement, and catch any exceptions that were generated by it. If you can resolve the cause of the error and try again, great, if not, fine; you probably couldn't have done anything about it had you known beforehand.

    Exception-based error-handling thus creates more efficient code by assuming it will work until it doesn't. Guard statements that return early cost processor time; they should thus be used primarily when the benefits of checking up front outweigh the costs; if you can determine in a few clocks that the input will never produce a valid output, but it'll take several seconds for the main body of the function to come to the same conclusion, then by all means put in a guard clause. However, if there are a dozen things that could go wrong, none of which are particularly likely and most of which require expensive execution, the "happy path" of normal program execution will be several times faster if you simply try/catch.

2
  • "By default, return codes are ignored; exceptions aren't.": Hm, this requires discipline: lazy developers will write try { } catch (...) (in C++) or try { } catch (Throwable t) (in Java).
    – Giorgio
    Nov 17, 2012 at 0:11
  • 1
    True, but it requires less discipline; it requires the discipline not to do more work to hide the error, rather than to do work to detect that there is an error. A lazy developer is much more likely not to do any error checking in either model, and in that case exceptions throw out while error codes are swallowed.
    – KeithS
    May 22, 2013 at 15:32
5

The fundamental difference for me is reading vs. writing:

Error code handling tends to clutter the intent: exception-based code is usually easier to read since the source focuses on the things that should happen, rather than what might.

OTOH, A lot of exception-based code is harder to write, because for a correctness analysis you have to argue about "unrelated" details, such as construction / destruction order, partially constructed objects, garbage collection etc.

How hard? This hard.

Generating meaningful exceptions can clutter the source as well, I have code bases where virtually every lines of code is followed by two or three lines building a literate exception.

For transferring meaningful exceptions to the top-level, they often need to be repackaged. When I click "rebicker cells", an error message like "Sharing violation on %tmp%\foo\file536821" isn't much more helpful than "Error 77".


I really don't have a preference, both options are equally ugly. What's worse: which one is better seems to depend a lot on the project, in hard-to-predict ways. In my experience, low-level hardware interaction is smoother with error codes (might be the clients...), low level data access is better with exceptions.

8
  • with exception you have the option to build a literate exception with lots of debugging information that can be inspected at runtime, but if you want to build a literate error value you have to have all functions to be aware about how to handle the smart error value. I don't see how that is a drawback of exception.
    – Lie Ryan
    Sep 25, 2012 at 9:20
  • 2
    @Lie Ryan: The "cleaner code and better error handling" is largely a false promise, it's a tradeoff. It looks amazing in a few sample scenarios, but in practice degrades to something that is close to well-planned error-code-based implementation. So yes, it gives you the option, but the advantage isn't as large as often made out to be.
    – peterchen
    Sep 25, 2012 at 13:04
  • 2
    Exception shines best when you want to abort the current task and display a message (to stderr or a GUI error box) when any error happens (which is all you can do in most user-facing programs); if you need detailed error recovery, that is going to be hard whatever strategy you use. IME the pitfall that you're referring, that code using exception degrades to something as complicated as error code, only happens in practice when you want fine-grained handling for all edge cases (which does happen sometimes), but I believe that's the exception rather than the rule (pun not intended).
    – Lie Ryan
    Sep 25, 2012 at 14:54
  • @LieRyan: IMO you put that very well - though from my experience I'd say the degradation is more common. I think the fundamental problem here is that exceptions vs. error code is a decision made very early, and significantly affects API's (even when using a global instead of return values - see the GOTW example).
    – peterchen
    Sep 25, 2012 at 16:07
  • As DSM puts it in his answer, when you write code with exception: put try/catch around a large chunk and then simply code application logic. By "a large chunk", it means a very large chunks, near the top, in the UI layer that are responsible for actually displaying error message. Intercepting an exception on it's way up are rarely necessary when the destructors does their job right. This style of error handling means there is only the throwers and one catchers on the top, and anyone in between can ignore error handling, it generally leads to very clean code.
    – Lie Ryan
    Sep 25, 2012 at 17:29
3

Let's put the implementation problems of many languages aside. The benefit exceptions have is that they can be centralized to handle all kinds(in theory) of exceptional states of the program. The problem they tackle is that exceptional things happen and can hardly ever be predicted. What is great(in theory) with exceptions is that you are safe as long as you:

  • Write exception safe code to highest degree possible
  • Catch the exceptional states via exceptions
  • Handle the exceptions accordingly
1
  • 1
    Grau, teurer Freund, ist alle Theorie, Und grün des Lebens goldner Baum. -- Dear friend, all theory is gray, And green the golden tree of life. (Mephistopheles and the Student in Goethe's Faust I)
    – marktani
    Sep 25, 2012 at 1:29
2

Exception-based code moves the error handling out of the main program flow. Instead the error handling is moved to either object destructor or to the catch construct.

The biggest advantage and also the biggest drawback with exception is that it does not force you to think about error handling. With exception, you often would just write straightforward code and let the the destructor does its job, and forgot about that one little scenario that the destructor does not handle and need to be done manually (which is clearly documented and therefore it's totally your own fault for forgetting). The same scenario could happen with error code, but when writing code with error code, you are forced to check the manual/documentation/source all the time and it is less likely you'll forget about that one little condition.

Yes, this mean exceptions might sometimes make it harder to write correct code.

Writing a bad code with error code is easy, writing a good code with error code is hard. Writing a decent code in exception is easy (because any errors terminates the program or is caught by a very high level handlers that could warn of the impending doom, instead of being passed around silently), writing good code with exception is really hard (because you're handling the errors an arms away from the place where the error happens).

1
  • Easy to forget exit codes
  • Duplicate code. Checking the error code and returning the error code when there is an error would cause lots of duplicate code to handle and lots to change if the return type changes. Also what to do when different types can cause an error?
  • Exceptions are ran outside of the code path which means there is no work checking the return value. But there is work in the binary somewhere when the exception code is ran.
  • Easy to see what functions handles what type of errors. You dont have to look through a lot of code, just scroll through catch.
  • More details (in certain languages). It has built in information such as what line, function, etc. Putting that into a error class would be tedious. Likely you would only write function name and hopefully the type or cause of the error.
  • Debuggers understand exceptions are errors and may pause when it hits one while you are debugging. Tools help :)
-1

If both are implemented perfectly the differences are not that much, however, usually nothing is perfect. If the handling of some errors is missing, in case of exceptions the program halts with a visible error, while the program with error testing just keeps running with undefined behavior.

If you develop a program and have an error you did not notice, what would you rather have?

  1. The program crashes so you start looking for and find the bug (or the program crashes at the beta testers, or maybe at the user so they can send in a bug report) (exception handling)
  2. The program keeps running, meanwhile silently corrupting some variables without causing any visible effects on your computer. However, it will delete all the users data when they run it, without them noticing when it happens. (error testing)
1
  • Well, I think it depends on the context. If the program is a web application, it is fine to see a stack dump in the browser and send a bug report. If the program is an avionic control software, it should better detect the error as soon as possible and try to recover: The user might not be willing to send a bug report while the aeroplane is falling.
    – Giorgio
    Nov 17, 2012 at 0:18

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