I think the only situation where exceptions help is in constructors, but there are better solutions to that problem, like explicit object construction methods that can return an error.
I think Go's approach to error handling is the best - explicit and in context. Rust's `Result` looks quite nice though I haven' tried it yet.
I've said it here before, but this is my classification of errors that exceptions are commonly used for:
1) Programming error: null pointer exception, library invariants broken, etc. These generally shouldn't be caught, and could arguably be replaced by fatal assertions, but that's typically not a polite thing to do in a library.
2) Non-deterministic failure condition: something outside the control of the calling code failed in a way that it cannot predict ahead of time. A network connection broke, a file was deleted unexpectedly, a device was detached, database server died, etc. There's something of an argument for annotating functions that can fail this way in the manner of Java's checked exceptions and it has a strong relationship with the IO monad in Haskell (and similar problems with encapsulation and hiding implementation details). These kinds of errors can sometimes be handled, and handling them with exceptions isn't much better or worse than error codes. An advantage of exceptions is that they makes it easy to abort the whole task and go back to the request handler (server code) or event dispatch loop (UI code). Exceptions can carry more state than error codes, too.
3) Business logic problem: the user, for want of a better term, of the program tried to do something that is not allowed / possible, and the program needs to abort the current task and inform the user of the problem. Exceptions are as good a means of aborting as most others. These should only be caught at the top level, whereupon they can be converted into something for human / 422 response / whatever consumption.
In almost all cases, you cannot proceed and there is nothing you can do in reaction to the error. Aborting and unwinding the call stack is usually the correct thing to do.
I haven't used Go (I'm about to for a small project, though) so I can't comment on the "if err != nil" idiom, but it seems like a step back by encouraging code duplication where it's not needed. I'm a fan of the pattern matching in rust, but it's still rather orthogonal to exceptions as it allows you to deal with an error when you try to access state whereas exceptions are most useful when they appear mid operation.
Go's implementation on JSON parsing is also.....
Platform::COMException
https://msdn.microsoft.com/en-us/library/windows/apps/hh7104...
Either your function can complete its task successfully, in which case it satisfies whatever invariants are required of it and returns accordingly, or it can’t, in which case it can throw an exception to indicate that inability to return successfully. In the success case, the calling code can then continue based on whatever returned value and/or side effects the function was supposed to provide. In the failure case, the calling code may well fail itself if it can also no longer complete its task successfully, and so on up the stack until you reach a level in your design that knows how to recover from the failure or stop the entire program gracefully, which is where you catch the exception.
I’ve never quite understood the idea that exceptions are similar to goto. Other than transferring control over potentially long “distances” within a program, they don’t seem to have much in common at all. To me, a much closer analogy would be returning early from a function on success, if you reach a point in your algorithm where you already know the required answer to whatever question your calling code was asking. Another similar situation would be using a break or continue statement to finish an iteration early within a loop, or adding extra guard cases to stop a recursion early, again if you already know the outcome and continuing with the full algorithm has no advantage.
That is the point, but my specific objection is to the dynamic binding and dynamic typing, not the returning. With “return”, even early return, you don’t really need to reason about where you’ll end up at all—it’s always the end of the current function. And the same goes for functions that throw exceptions directly.
The problem is that when you have unchecked exceptions, any function can throw, so every function call in your code is now also a potential early return, and for correct code you need to account for that.
This means that writing state-modifying code becomes unreasonably difficult, as you can no longer just write:
// A and B must be kept in sync and updated together
A = computeA()
B = computeB()
.. because it's possible that computeB() throws an exception, leaving the two out of sync.Yes, there are ways round this involving temporary variables. So you write:
tmpA = computeA()
tmpB = computeB()
A = tmpA, B = tmpB;
and home that neither an optimiser nor an intern removes the tmp variables. But wait! This still doesn't work, because your evil coworker has defined operator= on B to throw an exception!Personally I don't think that exceptions work very well in the presence of mutable state. Without mutable state, they're exactly equivalent to
Exceptions with mutable state needs to be coded transactionally, yes, and this is an excellent reason to try and avoid mutable state and use persistent data structures where possible.
.. which makes using the STL surprisingly hard, because you can get an out of memory exception all over the place.
In practice, I think this issue gets far more attention than it deserves, particularly within the C++ community. If you’re running in a particularly resource-constrained environment where running out is a relevant problem, you probably already take precautions about how you allocate those resources and adjust how you structure your program accordingly. You’re trying to avoid any possibility of those errors occurring, which means you don’t then have to worry about dealing with them unexpectedly all over your code.
For me, the more interesting discussing is around how we deal with errors that can be properly recognised and handled within normal execution. That means the interesting exceptions are those we throw ourselves and those deliberately thrown by other modules that we depend on, which we hope will be documented with each module’s interface. These things don’t just go off arbitrarily in just about any line of code we write, and they can be systematically structured, and we can write code that controls where mutations and other effects happen and where exceptions happen and how they interact.
It's even harder to get right concurrency with exceptions :D
No it isn't.
The bad idea was making language features optional.
Library writers are forced to either support all possible combinations, don't use any of them or use them and see their library being rejected by those that won't turn on the features even at point gun.
One of the things that made me initially enjoy Java was that there weren't features to turn on or off.
They don't play well with manual memory management or the C semantics C++ was trying to be as compatible as possible.
They don't play well with:
- constructors/destructors
- manual resource management
- low level code like UNIX signals
- allowing every possible type to be used as exception
So all of that made C++ exceptions poor cousins of how exceptions are used in other languages.
I have used exceptions in quite a few languages and rather use them (the alternative being sum types), but do understand that how C++ evolved they are an unwelcome feature.