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?

Richard Keller
  • 491
  • 1
  • 4
  • 7
  • 4
    Mandatory Raymond Chen link: http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx – peterchen Sep 25 '12 at 06: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 '12 at 07:27
  • 1
    and a bunch more from the *Related* list on the right, including [The modern way to perform error handling…](http://programmers.stackexchange.com/questions/147059/the-modern-way-to-perform-error-handling) – Péter Török Sep 25 '12 at 07:53
  • @peterchen I didn't understand the example provided in the end of [that link](http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx). What difference does it make whether the icon is visible or not? The (incomplete) object is not returned either way. – João Portela Sep 25 '12 at 14:35
  • Way to start a holy war :). – MrFox Sep 25 '12 at 14:41

10 Answers10

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.

Steven Schlansker
  • 1,616
  • 14
  • 10
  • 11
    The last paragraph is most important - "..when properly used, can ..." – mattnz Sep 25 '12 at 01: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" – whatsisname Sep 25 '12 at 01:21
  • @whatsisname: And those who cannot be bothered catching the specific exception they expected and catch and consume everything..... – mattnz Sep 25 '12 at 03:49
  • 1
    @mattnz at least they are easy to spot in a code review and some IDEs add logging in the auto complete of catch(Exception) blocks (so even the (cr/l)azy can find where a crash originates). – josefx Sep 25 '12 at 06:25
  • 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 '12 at 07:37
  • 1
    Couldn't you just write a function `checked_malloc` once that handles the error and then call `checked_malloc` everywhere instead of `malloc`? – fredoverflow Sep 25 '12 at 09:51
  • 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 '12 at 09: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); }` – fredoverflow Sep 25 '12 at 09:58
  • 3
    @FredOverflow: Yes, that works, as long as you only ever want to unconditionally abort. What if you need to clean up in one way in one section of the code, but a different way in another section? Suddenly your wrapper is not usable, and you are back to square one. – Steven Schlansker Sep 25 '12 at 20:25
  • @StevenSchlansker Lets see an example with non-framework functions. – Uri Abramson Dec 01 '15 at 19:08
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.

Simon Bergot
  • 7,930
  • 3
  • 35
  • 54
  • 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). –  Sep 25 '12 at 06: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 '12 at 06:28
  • 3
    How do Java's checked exceptions play into this? – fredoverflow Sep 25 '12 at 09:30
  • 2
    @FredOverflow I have never used Java, but I have the feeling that I wouldn't like this feature. – Simon Bergot Sep 25 '12 at 09: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 '12 at 10:13
  • @Simon If you refactor and change lots of stuff, you might also get tangled with where is the correct place to put the try catch... it sounds line a con argument to me. Exception in your example sounds like a GOTO style bad practice – Uri Abramson Dec 01 '15 at 19:10
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.

Secure
  • 1,918
  • 12
  • 10
  • "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 '12 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 '12 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 '12 at 00: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 '12 at 09:02
  • That's the whole point: Checking for the error is easy. *Then what?* -- This is where it gets complex. – Secure Nov 17 '12 at 09:03
  • I think we agree: If the absence of the file is an acceptable scenario, you can check it before: `if (file.exists()) { loadConfig(file); } else { loadDefaultConfig(); }`. If the absence of the file is an error, then `try { loadConfig(file); } catch ( )`. – Giorgio Nov 17 '12 at 10:00
  • 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 '12 at 10:21
  • Sure, if after exist() has returned true the file cannot not be opened, you get an exception. If the file can be opened but cannot be parsed, you get another exception. If the file can be parsed but contains invalid configuration parameters, you get yet another exception. – Giorgio Nov 17 '12 at 10:51
  • IMHO, the proper pattern should be for code to guard code with blocks that say "If an exception occurs in this block, run this code which will expressly invalidate any potentially-corrupted objects". Try/catch isn't quite appropriate because catching an exception is presumed to handle it (and acts as a barrier to further first-pass exception handling). Proper behavior should be to react to an exception, but without handling it. – supercat Mar 16 '15 at 23:52
  • @supercat that's what `finally` does – Esailija Feb 19 '16 at 11:57
  • @Esailija: Unfortunately, most languages lack any reasonable mechansim via which a `finally` block can determine whether the `try` block completed normally or via exception, and it's rather difficult for a `finally` block to react to an exception it has no way of knowing about. – supercat Feb 19 '16 at 15:58
  • @Giorgio "you do not intentionally try to open a file without checking" -> https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use – Bernhard Jan 31 '20 at 10:42
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.

samthebrand
  • 368
  • 2
  • 12
  • 27
DXM
  • 19,932
  • 4
  • 55
  • 85
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.

KeithS
  • 21,994
  • 6
  • 52
  • 79
  • "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 '12 at 00: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 '13 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.

peterchen
  • 1,127
  • 8
  • 15
  • 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 '12 at 09: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 '12 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 '12 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 '12 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 '12 at 17:29
  • This style of error handling relies heavily on the destructors doing their job right, therefore exceptions without a GC (like in C++) is largely useless. Many of the exception safety issues on GOTW seems to be due to destructors not being called correctly (due to no GC) or destructors not being written correctly, AFAICS. – Lie Ryan Sep 25 '12 at 18:06
  • @Lie Ryan: I see most degradation problems with code that needs to throw. Calling code often needs to translate exceptions every 2..3 call levels, but that's a minor issue. ---- The key point of that particular GOTW is that you can't make `T pop()`exception neutral if T's copy constructor throws (even if implemented correctly). – peterchen Sep 25 '12 at 18:16
  • Note that the copy constructor that causes exception unsafety isn't the one inside `T pop()` but the one that copies the return value of `T pop()` to an external variable, by that time `pop()` is already finished. IMO that isn't an issue with exception but with API design, because 1) designing interface is always a good thing, 2) there is an exception neutral solution with `void pop(T&)`, 3) it is also impossible to write `T pop()` with error code correctly if T's copy assignment can set error code (you're only safe in exception-less C because C's copy assignment can never fail). – Lie Ryan Sep 25 '12 at 21:28
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
zxcdw
  • 5,075
  • 2
  • 29
  • 31
  • 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 '12 at 01: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).

Lie Ryan
  • 12,291
  • 1
  • 30
  • 41
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)
vsz
  • 1,486
  • 9
  • 17
  • 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 '12 at 00:18