Everyone here seems to agree that "exceptions are for exceptional conditions". The problem is that when you get down to details, there is disagreement about what exactly is an "exceptional condition".
e.g. - you are trying to open a file for reading. The file does not exist. Is this exceptional? That depends on context, but the function that opens the file, being in an independent library, is usually designed without this context.
If it does throw an exception, some people complain that "of course it'e expected that file won't be there sometimes! that's not exceptional".
If it doesn't throw an exception, some people complain that "we tried to open a file, but didn't succeed, of course that's an exception". But if you want to avoid an exception in this case, you'll need to check for existence before opening (LBYL "look-before-you-leap"), and get into a race condition (TOCTOU "time-of-check,time-of-use"), which is really bad.
So it very often happens that you are forced by your ecosystem to use exceptions for normal control flow. Claims that you can only use it for "exceptional / unexpected" tend to be incompatible with a project in which you do not develop/control all of the library you use to your strict standard of exceptionalness.
Vague value judgments ("only for exceptional conditions") are the inevitable result of a failure to reason.
The semantic function of exceptions is just a way for summing additional values onto the return type of a function because it has results that are not contained within the primary type. In this way, they are a more general and better typed version of NULL (which has it's places - contrary to the modern dogma, these sort of features are needed due to inherent complexity). The standard ways of attempting to avoid this are to either use sentinel values that exist in your standard return type like fd == -1 for an error (thereby making your program less typed), or to create a top-level sum type for every aggregated function return type (cluttering your program with nominal types). Multiple values make the most sense, but those are ad-hoc product types, so you're eschewing the type system in favor of informal invariants.
The syntactic function of exceptions is to avoid constantly repeating (check for error, return error), which often leads to the poor practices of ignoring errors or calling a global exit(). One goal of programming languages is to automate, so it makes sense to capture this oft-repeated pattern. But problems arise when people end up forgetting that every function can have a possible return immediately following it.
It seems some syntactic middleground is needed to signal the complete return type of a function definition, and the possibility that a given function call may quick-return. Honestly (and I hate to say it), but Java probably started down the right track with checked exceptions, but being a B&D language it ended up being waaaay too verbose. And lacking a way to aggregate types along anything but the baked-in hierarchy, people fell into using generic and uninformative 'throws Exception'. And open types make it so there's little point trying to enumerate exhaustive causes. But that doesn't mean that one can't start with the idea of non-silent but syntactically lightweight exceptions and come up with something that avoids a lot of the pitfalls.
Overall, very good comment. My one point of disagreement is where you dismiss sum types because they clutter your program. Since sum types are the correct answer to this problem in theory, it seems to me that we shouldn't move on from that answer due to problems with implementing it in practice. Further, I think that the problem has already been solved pretty well in languages like Haskell that support type classes (like Monad) which make it so that you no longer have to worry about the sum types except when explicitly working with them (e.g. pattern matching on Left and Right). There's still room for improvement on working with sum types in Haskell (e.g. nested sum types can be annoying), but it's the best solution I've seen so far. Joel even makes note of Haskell's solution being good in the article, but only briefly.
> My one point of disagreement is where you dismiss sum types because they clutter your program ...
> There's still room for improvement on working with sum types in Haskell (e.g. nested sum types can be annoying)
What I dismissed is requiring sum types to be nominal. For instance, imagine that Haskell used the Scheme/Java representation of objects (ie everything is basically a member of one sum type, discriminated on a machine word in the header). We could then do things like:
type Foo = Bar | Baz
type Foo2 = Bar | Baz
Where Bar and Baz could be any type. Now both Foo and Foo2 are just different names for exact same thing, and in fact the names Foo/Foo2 are irrelevant when pattern matching the result of a function that's been declared to return (Bar | Baz). This philosophy is a bit different from Haskell in that it assumes that "everything is an object" (rather than the zero-overhead structs of Haskell), and it implies that every sum type defined this way can only contain one branch for each included type (without names, there's no way to differentiate them), but a merging of the semantics could definitely be hammered out.
I think Rust (being not quite formed yet) could benefit from taking a stab at this, having every sum discriminator be globally unique, and every non-sum type having an associated global tag that only gets prepended when it is promoted to being an anonymous branch of a sum. The immediate use I envision is being able to create ad-hoc type hierarchies that are descriptive rather than prescriptive.
I'm really fond of the idea of Nullable types (where you have to unwrap them by checking if they're null or errors to get the value). I don't think any non-functional language other than Rust has attempted to work this into the way they work yet, but it strikes me as a powerful mechanism that could also allow for deferring error handling to the site most capable of dealing with it.
Scala has the Try [0] and Either [1] types that can be used to achieve this. However, I believe Scala also allows any type to be NULL. In Rust there is no NULL so you don't have to worry if a value is ever NULL unless it is explicitly wrapped in an option instance.
If you use Guava, you even get Optional<T> for Java. It works pretty well, but is obviously something you have to work into your API and can't easily use after the fact.
Completely agree with the gist of what you are saying, but exceptions are MORE than just a sum/product/extended value type: They are a non-local return. You're obviously aware of that, from the Java comment, but seem to consider that fact an implementation issue of Java, whereas most people consider this a defining attribute of exceptions.
They're restricted to only escaping currently active stack frames, not jumping anywhere like full continuations. They're basically equivalent to having a default cascade of
if (ret == -1) return -1;
after every function invocation, which is why I was calling that aspect a syntactic feature.
It's this implicit return after every function that trips people up (when prematurely exiting from stateful computation). So what I was saying that making the call of an exception-throwing function (slightly) more verbose would alleviate that and pay for the complexity where it was used.
You forgot to mention one of the main criticism to use exceptions for errors: the processor penalty.
As with much of the exceptions debate, it’s important not to over-generalise here.
For example, if you’re writing in a compiled language like C++ and your compiler uses a table-driven implementation for the exception mechanism (as most modern ones did, the last time I checked) then there isn’t necessarily any direct runtime overhead at all when no exception is thrown. In fact, the non-exceptional code path can even run a little faster than equivalent code with manual error handling via return codes, if conditional logic for propagating error codes can be omitted at all the intermediate levels between the one(s) where exceptions are thrown and the one(s) where they are caught.
On the other hand, the possibility of an exception being thrown might interfere with some optimisations. Also, the jump tables can be huge: I once saw the compiled output for a moderately large code base drop in size by 1/3 just from compiling it with exceptions disabled.
In short, there are a lot of factors at play, but anyone who parrots the line that using exceptions always slows things down has never spent much time looking at what actually happens with real compilers. And of course, this is only in one type of compiled language, which doesn’t necessarily imply anything about the performance characteristics of other languages (which vary widely).
Agreed that it's a poor overgeneralization, and often trotted out when it's absolutely incorrect, but it's worth noting that on Windows the existence of Structured Exception Handling in the OS prevented this kind of optimization for a long time. It may yet, in fact, but I've been out of that world for a long time.
This probably extended to things like the xbox as well.
> you are trying to open a file for reading. The file does not exist. Is this exceptional?
It's actually handy to have an API that includes both situations. For example, parse and tryParse in C#. Parse will trigger an exception if it fails but tryParse will not. When used in your code, this actually documents what the programmer is expected. If an open and tryOpen operations existed, you would know whether or not the programmer expects the file to exist or not.
However, an important point is that a "valid result" maybe a status indicating what went wrong. For example, a function that reads an http url may return (OK, 200), (NOT_FOUND, 404), (REDIRECT, 301) and so on. But in addition to that, the function may also throw an exception if a dns lookup error occurred for example.
Opening files on the other hand, can fail unexpectedly for a billion different reasons. Most of which an opening function can't detect or do anything about and therefore can't return a valid result if they occur. Therefore it must throw an exception.
> the function may also throw an exception if a dns lookup error occurred for example.
What's wrong with (DNS Lookup Failure, -17), as long as it is documented? why doesn't 404 NOT_FOUND merit an exception, yet a DNS lookup failure does?
(I can argue both ways, I'm just trying to point that there's no clear criterion)
> therefore can't return a valid result if they occur. Therefore it must throw an exception.
But open() in C does return a valid result on any of those billion reasons: (fd -1, errno reason). Yes, errno is returned in a global variable, but it is still a return from the open() call. Therefore, exceptions are never needed according to your logic?
>> the function may also throw an exception if a dns lookup error occurred for example.
> What's wrong with (DNS Lookup Failure, -17), as long as it is documented? why doesn't 404 NOT_FOUND merit an exception, yet a DNS lookup failure does?
Because then every function that could potentially cause a dns lookup failure needs to have that error code documented. Then ask yourself this, what's wrong with having (Out of Memory Failure, -1234) in addition to DNS Lookup Failure and HTTP Status codes?
Think of yourself as a http function. You know about the http protocol and therefore from your perspective a 404 NOT_FOUND is a valid result. However, you do not know about DNS lookups or memory allocation therefore if a problem occurs in those areas it is exceptional -> exception.
On the other hand, if you were a memory allocator function then returning out of memory instead of an address to the allocated memory would be fine. Because memory handling is your job.
> But open() in C does return a valid result on any of those billion reasons: (fd -1, errno reason).
Syntactically valid, but not semantically as -1 isn't a valid file descriptor.
> Then ask yourself this, what's wrong with having (Out of Memory Failure, -1234) in addition to DNS Lookup Failure and HTTP Status codes?
Indeed, I see nothing wrong with this.
> However, you do not know about DNS lookups or memory allocation therefore if a problem occurs in those areas it is exceptional -> exception.
This is where the disagreement lies. When I imagine myself as an http function FOR MOST USES, I imagine a "full service" function. You give me a URL (+post data), and get back a return code and a page. This functionality is sufficient for 99% of web uses.
If I can't get a 20x (possibly after following a 30x or 401 unauthorized response), the user in 99% of the cases does not care what the reason for failure is - they just need to know it failed and some reason to log/display.
Turn it the other way around: Why should a user of an http library care about internal implementation details like DNS resolving, to the point of needing to include an explicit check for them? And if you're advocating for "catch all exceptions", how is that different from a multiple-return-value?
> Syntactically valid, but not semantically as -1 isn't a valid file descriptor.
So what? It is a valid return from the "open" function. No, you can't use it for reading or writing or ioctl - but then, whether or not you can do that to any other returned descriptor depends on what that descriptor is (/dev/random? /dev/null? a socket?)
I'm not disagreeing that exceptions can be useful. I'm just disagreeing that what is exceptional vs. what is regular is a well defined thing.
It's possible for a library to return an error to indicate that a file is not found when you try and open a non-existent file. Then it's possible for a function that calls the open file function to decide whether that error should be translated into an exception i.e. should that file being missing break out of the current execution? There's no race condition there.
I don't think anyone reasonably expects a library to know the context of the file being opened and its importance in the logic of the rest of the program.
It seems to be a pattern in Python APIs to allow the caller to decide if a given function call should throw an exception when an error occurs. For example,
Going one step further on the file example, I had a college professor who wrote a function read from a file that had no end except an exception thrown by the read because the file was at its end[1]. He said the end-of-file was an exceptional circumstance for a function that expected to read and process a line.
I doubt anyone would say end-of-file is unexpected, but I am not sure I would say it was exceptional.
1) something like this (it has been 20yrs)
init data structure S
open file A
loop
read line from A
process line and add to S
next
catch EOF
close A
return S
catch file-not-found
return empty S
In Java, everyone would tell you this is wrong. In Python, this is the normal way to end an iteration (specifically, throwing StopIteration). It's not a huge leap to see a stream as an iteration over bytes. So, what's considered exceptional seems somewhat culturally dependent.
The only time you need to think about StopIteration is if you're consuming from an iterator in some unusual way (eg writing a "get me the single item which is in this iterator, or blow up if there are zero or more than one" function) or if you're writing an iterator which is not built out of building blocks which already know how to stop an iteration.
I think Common Lisp has a good approach with its restarts system. I try to write something to a file but there is not a enough disk space? How about telling the user, then invoking a restart when the user says, "There is more space available!" and continuing execution as if nothing went wrong? The problem with exceptions is that there is no way to recover from them in most languages, because the exception handler is found by unwinding the stack.
What I do not like about the "check return values" approach is:
1. It means that client code must understand how to handle error conditions. No disk space? Well whoever called the top-level function that invoked write needs to figure out what to do if there is any chance of recovery. It is a maintenance headache that can quickly accumulate bugs.
2. In both Java and C++ there are functions that cannot return values: constructors, and in C++ destructors. No, it is not acceptable for a program to crash just because a constructor call failed. No, it is not any better to have every class have a special variable that indicates that the constructor failed. No, having empty constructors is not the answer, and it is certainly not going to help with destructor calls (the C++ standard library actually requires some destructors to silently fail because of the issues with error reporting).
Restarts are perfect! This bothered me greatly when I read Code Complete. It doesn't discuss error handlers (which restarts are a form of) in error handling chapter at all.
I really wish more people knew about this option. Maybe it would even get into mainstream languages.
I think his point of there being easy syntax for multiple returns is critically important to make this sort of error handling non annoying - which Go does remarkably well.
I think this factor has a lot to contribute to the fact that you get the warm fuzzy feeling after your code compiles. You feel confident that you have already handled all the error cases (that you care about) in your code.
<nitpick type="obligatory">The name of the language is Go, not "Google Golang"</nitpick>
That said, this was, for me, the single weirdest thing to get used to when starting to program in Go, coming from a mostly Python/Java exception-style background. (I imagine it's easier if you're a C programmer).
However, once I got into the swing of it, I realized I really, really like Go's error handling, and I can't imagine going back to Python's exceptions voluntarily. Returning errors makes code much more readable, and it also reduces the risk of code suddenly failing because of an exception that got thrown somewhere that you can't even find easily.
Go also gets a more subtle point correct: errors are interfaces, not types, which means that you can use any type as an error, as long as it supports the "Error() string" method. This is irrelevant 99% of the time, but I've seen a few pieces of code which utilize this feature very effectively.
I'm endlessly amused by the irony of a search engine company naming their projects unsearchable words. Many site search engines won't even consider terms less than three characters long.
For all these language issues, I generally preface the query with the language name. "Python3 Django", "Haskell Quickcheck", "C++ Boost", etc. It makes things so much easier for the search engine to give me Good Results (tm) when it has that extra bit of context.
That was my first thought as well, glad to see that Joel is on the same page.
After writing a fair amount of Go recently, I've come to believe that usage of explicit error returns only appears to increase code density. In reality, it exposes the complexity of correct error handling and forces you to factor your error handling logic accordingly, rather then letting you sweep it all into a few top level handlers.
But why is this preferable to an either monad? The majority of the time when a subfunction fails with error, I return the error from the function calling it, so I'd rather just have a do notation or something take care of that for me.
> You feel confident that you have already handled all the error cases (that you care about) in your code.
What about the errors you don't care about at the time, but realize later than you should care about? Do you go back and add them? This process seems error prone to me.
From the Go FAQ "[Excpetions] also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional."
So the problem is not with exceptions, but how some programmers use them.
The discussion around that time was interesting (though distributed around the blogosphere); Ned Batchelder had recently argued the opposite (and updated the argument specifically to take Joel's article into account)[0][1]. It was at about that same time that Damien Katz was becoming firmly convinced that Erlang and crash-only behaviour would be the way to go in designing CouchDB[2][3]. (Both Damien and Ned were at Iris/Lotus working on Notes and Domino, and I was a Domino dev at the time.)
If you're catching exceptions all over the place, or worse using them for flow control as part of normal operations, you're doing it wrong. Exceptions should indicate a major error that you can't easily recover from and as such should be caught and logged at the top of the stack, i.e. the main thread run method or request handler.
When used that way, they give you very useful information as to what went wrong and where, while making your program more robust and resilient to errors. We've found this to be the case time after time at https://starthq.com, which runs on Node but uses fibers via https://github.com/olegp/common-node.
"Exceptions should indicate a major error that you can't easily recover from"
Maybe in Java and C++, but in Common Lisp we have restarts that allow you to recover from an exception. I like to use the example of attempting to write to a file when the disk is full, because:
1. It is possible to recover from the exception (e.g. ask the user to delete some files)
2. It makes no sense for the I/O library to do all the things needed to recover
3. It is a maintenance headache for client code to do all the things needed to recover
With restarts things would look like this: the I/O library would set up a restart for write that would retry the operation, the client code would catch the exception, prompt the user to free some space, and when the user indicates the space is free the restart is invoked. The I/O library knows the right way to restart the operation, and client code knows whether or not that should happen, and you get code that does not just quit over a disk being full.
I think most people have been brain damaged by checked exceptions in Java. It comes with this expectation that need to have to catch block in almost every single function. But if you do that, you've just recreated error codes and multiple returns!
Exceptions are meant be thrown frequently and caught very infrequently. Catch in the few places where recovery is possible and where you can log the error. That's it.
If you're catching exceptions all over the place, or worse using them for flow control as part of normal operations, you're doing it wrong.
That is a subjective view, and certainly not a universal one. In Python, for example, exceptions are routinely used for flow control purposes; see StopIteration.
I am not a huge fan of exceptions per se, but it’s important to understand that they are a heuristic for minimizing ‘worry’ about things that are unlikely. I am not saying it’s a good thing, I am saying it’s the way people naturally work.
Let’s say there is a function that’s 20 lines long, and if you did a thorough analysis of possible error conditions, regardless of likelihood, you might come up with 50 or more.
We are not going to write code to address all 50 possibilities from the start. Instead, we are going de facto to wait and see what fails in the real world, and address them as we discover them, because we value our time. We make an economic distinction between a 1-in-1,000 problem, and a 1-in-1,000,000,000 problem.
Is this un-robust? Yes. Are we allowing exceptions to be a control-flow catch-all? Yes. Would a Go-like approach of simply returning error conditions reduce bugs? Probably. But it’s important to recognize programmers’ ‘revealed preference’ for exceptions.
50 error conditions in a 20-line function doesn't really sound even remotely realistic. Probably only 5 of the 20 lines are actually calling functions that might return errors, and in most cases, we don't care what type of error is being returned.
So if we're talking about 5 error-checks in 20 lines, then yes, we absolutely should write code to address them from the start.
I mean, I can understand not dealing with errors from memory allocation failing, or even possibly failure to write bytes to disk, depending on the situation (e.g., if those fail, you've got bigger problems to worry about than your error handling -- and it's not like exceptions are probably helping you to recover anyways).
But for stuff like network communication, writing to databases, etc., you had definitely better be addressing all error possibilities from the start, because these things fail all the time.
Did someone just turn off the SQL machine half way through the query?
Can we find the server?
Are there any rows?
Did the SQL compile?
Do I have rights on this table?
Have you just terminated me as a result of a deadlock?
Did you return a Null when I was expecting a value?
Did you return a float when I was expecting an int?
Did my value just overflow?
Did you just return 0 and I tried to use it in division?
Did I just try to access the session but some other idiot clear it?
Did I just try to call a method on an object that is in fact null?
And that's all possible in a three liner off the top of my head. I'm sure there's plenty more than that that are possible! I didn't even start on the file ones...
Is the network up? / Is the connection to SQL up? / Did someone just turn off the SQL machine half way through the query? / Can we find the server?
These are not errors, just normal business logic that has to be handled:
Are there any rows? / Did you return a Null when I was expecting a value?
These are not runtime errors, they're just debugging during development (with possible exception of overflow, depending on context):
Did the SQL compile? / Do I have rights on this table? / Did you return a float when I was expecting an int? / Did my value just overflow? / Did you just return 0 and I tried to use it in division?
And likewise, these all just have to do with the design of your program, which you either know you have to deal with or not:
Have you just terminated me as a result of a deadlock? / Did I just try to access the session but some other idiot clear it? / Did I just try to call a method on an object that is in fact null?
I already said that you may not have to worry about things like memory allocation errors, depending on your needs. Most of the stuff listed above is either redundant, or has more to do with the design of your program. I stand by my point that, in most programming (say, back-end web stuff), you're handling more like 5 errors per 20 lines, not 50 per 20.
And that, yes, those 5 (or however many) errors should be planned for from the start.
Functions should be scoped appropriately so that they only deal with one thing at a time. A function that checks for network connectivity isn't going to be checking for missing rows. Those are different problems, and should be handled by different functions.
Using your example, we have a function that queries a database. It will be given a valid database connection, and return the result of the query.
There is no "valid database connection" logic, since that is handled elsewhere. There is no result validation logic, since that is handled upstream. This function only cares about A) querying and B) returning a value (possibly null). The only exceptions that is handles is when something specific to it's domain goes wrong - for example, unauthorized access to a table. That is an error that is above networking (the connection worked fine) but clearly not a data validation problem (no data), so we handle the exception here.
If you find yourself throwing exceptions "across problem domains", that's a good indicator that your functions are doing too much.
You can't just assume that a valid database connection will remain valid the entire time you're using it. The network or server is free to go down at any time. In practice, this rarely happens, it's an exceptional condition. Exceptions are the best way to model these kind of errors.
Sure, but the query goes through the layer of functions that actually deals with the database connection. If an error occurs, that layer is responsible and the "higher" layers simply pass on the exception that is thrown.
Point is: the function that deals with database response should never be responsible for dealing with database connection errors.
The Haskell solution is to decompose the problem into the pure and impure parts while using types to eliminate a number of these. Then you end up with general classes of exceptions which can be handled using pure Maybe types, perhaps, in various locations as appropriate.
Resource availability (Is the network up? Is the connection to SQL up? Can we find the server? Did I just try to access the session but some other idiot clear it?)
Incomplete response (Did someone just turn off the SQL machine half way through the query? Have you just terminated me as a result of a deadlock?)
Result semantics and types (Are there any rows? Did you return a Null when I was expecting a value? Did you return a float when I was expecting an int? Did my value just overflow? Did you just return 0 and I tried to use it in division?)
Translation to intermediate language (Did the SQL compile?)
Assumptions about remote state (Do I have rights on this table? Did I just try to call a method on an object that is in fact null?)
Smart code handles these all separately. It's crazy to try to bundle them all into one function.
This is a problem of architecture. Should you mix error handling with application logic? You have to handle errors somewhere, but they pollute the otherwise clean problem-solving code. One solution is to just let the exceptions bubble up and the other is to handle them immediately. If you let them bubble up, you can have a separate module that can do the right thing, notify the user, restart the app or something else. This way, the error handling is somewhat cleanly separated into its own thing. Sometimes you want to handle errors immediately, because only the code where the exception happens, knows how to deal with it. This is where return codes might be better. A lot of it depends on the desired behaviour. Sometimes you want the software to immediately exit or restart if there is a critical exception, sometimes you can safely ignore errors if real-time experience is more important. Sometimes all you want is to log the exception and/or notify the user. Does anyone have experience with aspect-oriented programming and does it help solve any of these problems?
I think there is reasonable consensus about some software engineering best practices:
1. Avoid action-at-a-distance and side-effects that are hard to reason about.
2. Use immutable objects and values rather than references and pointers.
3. Avoid intricate control flow with many branches.
4. Greater isolation of processes/threads (actor model).
5. Use systems and platforms with simple and strong guarantees (e.g. ACID) that are easy to reason about. Special cases and nuances to the underlying platform should be avoided.
6. Use tools with good support for static analysis (e.g. a good type system with a compiler that can understand it).
Exceptions seem to violate #1, #3, and #6.
These rules only apply to software engineering; that is, the reliability, robustness, long-term maintainability, and total project development cost (including maintenance and support).
Of course, there are other considerations, such as: performance, the time to achieve the minimum viable product, how much developers like the tools, how "hackable" it is, or utility for research purposes. These other concerns may be a good reason to violate the above rules.
"Monday, October 13, 2003" - more like "the old new GOTO"; but apart from the out-of-the-blue rant about PHP (version 4, I guess?) at the bottom, this still seems applicable.
His argument is really against using exceptions as part of normal operation, which I agree with. I've noticed when writing plugins to big monolithic programs like 3DS Max and game engines that these tend to throw and catch exceptions as a matter of course, which is a pain when you're trying to debug your own plugin code.
An exception should mean "normal operation cannot continue", and should signify a bug in your code. As such, it is an exceptionally good software development tool.
Exceptions aren't a debugging tool. You should use asserts for debugging. These asserts are then disabled via compile switches for the production version. Or you can write unit tests.
This is NOT OK. You're swallowing all exceptions and assuming that they are your special case.
The fix is to have a specific nonambiguous name for your exception, so that other error conditions still work properly. As examples consider the StopIteration and GeneratorExit exceptions from Python's standard library. (See http://docs.python.org/2/library/exceptions.html for a list of built-in exceptions.)
Exceptions for control flow are a slippery slope. in the socket case, it would be better to parse a terminator frame from your recieved data and just do socket.close() like normal than to hack a timeout or internal socket close, or negociate a fixed transmission length in advance.
This is on the basis code like this is hard to debug, moreso than any glorious exception free master style. You aren't guaranteed to have recieved your entire transmission in your exception block because you are catching any recieve exception. You do want exception handling here, but not as control flow, you want it as damage control if you get an unexpected early termination, not when you get desired behavior.
I don't use exceptions for normal flow. I use them for errors.
While it is true that exceptions create innumerable code paths through a function, RAII makes that manageable. If you're not taking advantage of RAII in your C++ code, you might as well be writing C code.
I don't find this true at all. Haskell exceptions don't look like exceptions in other languages, so it's easy to get hung up on the fact that error is generally used only for unrecoverable errors. But that's why it's called error, not exception. In Haskell, Either and Maybe are used where I would normally use try/except in Python.
The only exception to this that I've found is that error is something you may need to deal with in the IO monad, where in Python it would still be handled via exceptions.
Well, I didn't think of Maybe and Either as exceptions, but rather considered undefined/error. The exceptions from Control.Exception are probably the closest to exceptions in other languages, and I think those are built on error.
In C++, use exceptions when things go so badly that you have to metaphorically pack things up and send some people home. Because that's what exceptions are good at.
For example, I used to work on a system where there were various threads each controlling a different piece of hardware. But the hardware was hot-swapable, so it was possible that suddenly a thread would find itself trying to talk to hardware that was no longer there. We would throw an exception at the low level hardware access point, at catch at the (nearly) highest level of the thread. This allowed us to reclaim memory and other resources used by the thread in an orderly fashion, and return to a "ready for hardware insert" mode. This was key since the other threads were doing just fine and the overall program needed to continue.
Certainly not. In Python, for example, exceptions are used to handle exceptional conditions.
Here's an example. Say I'm writing a function and I have a dictionary I need to access values from often. Now say that 85% of the time the key I need is in the dictionary, but 15% of the time it is not. I could do this:
if key in my_dict:
execute_operation(my_dict[key])
else:
pass
So that if the key is not in the dictionary I do nothing. But this can be expressed more clearly like this:
This is considered to be clearer because it expresses the fact that the key should be in the dictionary, but in some cases is not. And interestingly, performance reflects this. The first method is more efficient in cases where the key is usually not in the dictionary. The second method, on the other hand, is more efficient when the key is usually present in the dictionary.
So, in summary, exceptions are used in Python to represent cases where successfully executing the code within the try block is normal, but exceptional conditions could once in awhile occur. Almost all uses of exceptions occur for conditions where the program should not crash. When the program should crash we just let the exception bubble up to the top level where we gracefully shut down the program and call an exit function.
Unfortunately, the second one eats an uncaught KeyError raised deep inside execute_operation, not just my_dict[key] failing. Which may be easy to overcome if you wrote all the code and libraries that execute_operation calls, but nearly impossible otherwise.
As a C programer i consider the first method to be clearer and am still unconvinced that exceptions should be user for anything other than unrecoverable errors; exceptions have the stack unwinding feature.
> [as an alternative to exceptions] "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
Was he being serious? I'd rather not wade through all that error checking cruft to see normal flow. Exceptions (or goto) don't create buggy hard to read programs, people do.
I disagree. Building an error checking scaffolding around every possible and unlikely error obscures the expected flow of control. If I checked for every possible error condition before I started the engine of my car in the morning it would take me an hour to get out of the driveway. I'd much rather have the car tell me when something was wrong and not start. If the system you are using already throws exceptions, you might as well use them too.
The advantage of exceptions is the ability to pass back the error and make mandatory dealing it (even if ignoring).
Exist a way to have both styles, cleanly?
I have thinking in how could look a language where objects, like in UNIX pipes, have stdout & stderr, and if stderr is consumed (returning codes) the exception is handled, but if stderr is not consumed, then raise it?
Most of the discussion about exceptions tries to think about it in abstract terms, which doesn't work. The point of exceptions is a very concrete one: often the code that runs into an error and the code that handles it are separated by ten levels of function calls. Without exceptions, the logic for detecting and passing on the error has to be duplicated in every function in every one of those levels.
Exceptions are arguably the one feature of C++ that isn't just syntax sugar over C, the one feature that makes the language fundamentally more expressive: they reduce a certain kind of code complexity from O(N) to O(1).
Forcing a function call to handle an exceptional condition when it doesn't know how doesn't really add anything but bloat to your source code (violating DRY). It's interesting the author mentions goto, because without exceptions goto is often the most reasonable but more error prone way to handle cleanup. C++ solves this using RAII, if you need something to run before the end of the scope, stick it in a destructor. This is very easy and far more composable.
While I'm not familiar with the Erlang paradigm that inspires it, the dire[1] library in Clojure is designed to decomplect error handling from code that may generate an error. I like this approach a lot.
For extremely low values of "easily". C#'s lack of pattern matching and tuple syntax makes dealing with tuples in C# a complete pain in the ass. It's ugly, annoying, code, in general.
P.S. You can do this in pretty much any language. Just define pair, triplet, etc.
Sure, exceptions are bad most of the time. But sometimes they are really useful, like in heavily recursive code, i.e. recursive parsers. Catching exceptions in a single top level function and throwing in every other one makes code much cleaner, since you don't have to propagate and handle errors on every function call and you have single exit point on top level function anyway.
e.g. - you are trying to open a file for reading. The file does not exist. Is this exceptional? That depends on context, but the function that opens the file, being in an independent library, is usually designed without this context.
If it does throw an exception, some people complain that "of course it'e expected that file won't be there sometimes! that's not exceptional".
If it doesn't throw an exception, some people complain that "we tried to open a file, but didn't succeed, of course that's an exception". But if you want to avoid an exception in this case, you'll need to check for existence before opening (LBYL "look-before-you-leap"), and get into a race condition (TOCTOU "time-of-check,time-of-use"), which is really bad.
So it very often happens that you are forced by your ecosystem to use exceptions for normal control flow. Claims that you can only use it for "exceptional / unexpected" tend to be incompatible with a project in which you do not develop/control all of the library you use to your strict standard of exceptionalness.