However, exceptions do simplify some situations, such as when an unrecoverable error happens at the bottom of a deep call stack. Exceptions make it simple to inform the user/system that an error has occurred without adding error checks to every function in the call stack. I believe this use-case justifies the feature, especially if you can tolerate the performance loss which might not even be that bad [0].
Having the compiler check error codes is one of the reasons I enjoy Haskell so much: the type system makes you handle failure (amongst other things).
[0] http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf - Section 5.4.1.2 - the table approach to exception handling has no run-time cost during normal (non-exceptional) program flow.
The actual problem is the ramifications on the code structure. A deep call stack with a lot of implicit context is a problem in itself. Exceptions imply a temporal coupling from error occurrence to error handling. This is a subtle but severe problem. It might start out as a noticeable maintainability problem but it's likely to quickly become a performance problem as well...
Could you expand on this? I understood your statement as "Exceptions sort-of force you to handle an error the moment it occurs", but I'm having trouble seeing why this would be specific to exceptions and not the case with other error handling solutions.
(I don't mean to sound like a big exception-defender – I prefer ML/Rust-style Result types)
Looking at errors as just data avoids the "exceptional vs non-exceptional" hair splitting, and gives a lot of flexibility for code structure. I don't necessarily disagree with the Result type viewpoint as a return type from functions, but I also find it pretty pointless. Very often the best action is to separate out errors from successes into different tables immediately. A built-in Result type couples them, and encourages keeping them coupled.
Now you could argue that you can do that with Exceptions, too, by catching them immediately and treating them as data. In which case I want to ask "what's the point then?" and also refer to my topmost comment. Exceptions have a significant cost in infrastructure even if you don't actually use them...
What is the point of sorting or grouping them?
Any chance you have another example?
Could you share another example where accumulating errors makes sense?
A real life example: Asynchronous webservers usually have worker threads for I/O. I/O errors need to be routed to the originators of the I/O requests (which are in other threads). Exceptions cannot do that.
Here's the general point: Whether any given thing is an "Error" is highly subjective and context dependent. But clearly the error is data, so why take away the possibility to process it like any other data?
IMO, using exceptions doesn't preclude treating errors as data.
In your example with parallel processing, you could throw an error as exception, let it unwind the whole stack, catch it and return as the result of the task.
This way you don't couple the code that does the useful work with the code that handles errors.
In other words, I don't understand why you dislike exceptions.
IMO they are a good tool for any task that doesn't require different actions to handle different errors.
They introduce additional control paths which are hard to reason about. In many languages these are not even explicit. Exceptions require an additional syntax to handle them. And having exceptions requires significant language infrastructure and comes with a huge toll on the structure of software projects. See my topmost comment.
> In your example with parallel processing, you could throw an error as exception, let it unwind the whole stack, catch it and return as the result of the task.
Which would catch only the first encountered error per thread. And not catch all the other errors that we could encounter and report after that.
"And not catch all the other errors" I think, in most cases after an unexpected error happens it doesn't make sense to continue the task and report more errors, most likely induced by the first error.
Don't get me wrong, there are plenty of cases when returning errors as values is the best fit and using the exceptions would be a disaster, but my point is that, IMO, the situation when errors abort tasks and are handled in more or less the same way is much more common.
`(Optional<A>, Optional<B>, Optional<C>)`
– each can fail or succeed independently. But you can't do that if you're using exceptions to signal errors – it's like returning `Optional<(A, B, C)>`
, you get either all the results or nothing. Good example, thanks! function f (...) {
val a = something.a();
val b = something.b();
val c = something.c();
return (a,b,c);
}
If the exceptions can occur in the calls assigning to a, b, or c and are handled locally (here, in f) then you can construct that first case. It's only if you throw the exception up one level higher that you end up in the latter case. As written, it is more like your latter example. But with a modication: function f(...) {
val a = default_a;
val b = default_b;
val c = default_c;
try {
a = something.a();
} catch {
a = error_value_a;
}
...
}
You can get the former case.Exceptions are in no way simpler than C-style error codes, but in many cases they might be easier to work with.
How exactly do you distinguish "exceptional" from "non-exceptional"?
His definition depends on the notion of Design by Contract. An exception is an event that causes a function/method to fail because it is unable to satisfy its contract. The caller is then responsible for cleaning things up (so it can satisfy its contract) or it also triggers an exception resulting it its failure.
In the context of Design by Contract, the list of things that can cause an exception include:
* hardware/OS errors
* calling a method on a null reference
* calling a method that itself fails
* discovering a pre-condition isn't true
* discovering that a post-condition isn't true
* discovering that a class-invariant isn't true
* loop invariant failure or lack of progress
* assertion failures
* explicit triggering of an exception
I highly recommend Object Oriented Software Construction.I do, too. I had read most of the book [1] some years ago. It is really good. But readers should know that they need to be ready to put in enough time to read, understand and digest it, not just because it is a thick book, although it is that, but because, true to Bertrand Meyer's style, it is detailed, systematic, thorough, etc. It's not one of those books where you can read it in a few days and then start applying its stuff to your work, nor is it for casual programmers who are only into the field to make some quick bucks.
[1] https://en.wikipedia.org/wiki/Object-Oriented_Software_Const...
BTW, the book may have an Easter egg in it.
What counts as intended is a question of definition, but in concrete examples of local reasoning within a function this question is usually easier to answer. Thinking about it in terms of (explicit or implicit) contracts, like Bertrand Meyer, sounds like a smart idea.
I'd beg to disagree.
As for the general statement of your post, I like how you're talking about stakeholders and (specification) bounds but I don't think it's an actionable viewpoint. The idea of "Bubbling up errors" inside a program is caught in the software reuse mindset but it's not possible to take action on encountering a true error (here "true" means "outside specifiation"). That's because the state of your whole program is undefined after the encounter. Which means that the best you can do is to call abort() and hope that the program will end with some info for debugging.
(Unless we are talking about a VM or sandboxed code, in which case again you could just call some equivalent of abort() from the code inside and "catch" that in the host code and restart the VM or something).
To clarify, in the context of the article, "making errors observable from the outside" is not meant at the per-function level, but meant at the level of the whole program, which includes abort(). I should improve the wording there.
abort() is perfectly legitimate if you can't recover within the same process. It's not at odds with routing to the right stakeholder, as long as the parent process catches the crash and does appropriate error handling (e.g. "Send a crash report" dialogs or other watchdogs).
Regarding textual logging, I'm not sure exactly which part you disagree with. :) I'm aware that people commonly write regex-based extractors, and I've done the same, but having the choice I'd always go for a more schema-ful error reporting channel. That's more lightweight both for reading and for writing it, and avoids a whole class of regex bugs.
Regarding textual logging, I think it works wonderfully and I don't think it's at odds with schema-ful reporting. One good way is to include error codes. Informal messages are at least as important. They are ergonomic to humans and their meanings are easy to look up with a web search.
Of course textual logging can always be supported by other means, like crash dumps.
There's no need to be dismissive. I refer to simple as simple to use, not simple to conceptualise.
I would say exceptional is a situation that is rare and unexpected. When saving a record to a table with a unique constraint, I would expect the constraint to prevent saving but I would not usually attempt to handle/recover the app running out of memory.
So it's situational and I'd say the distinction is between whether you will be explicitly handling the event as part of normal behaviour. The more stable the app needs to be, the more situations need to be handled.
The GP, along with many other people, are trying to get software professionals to standardize on a definition of simple that means something like "composed of a single element; not compound" or "easy to reason about".
Under such a standard, it makes no sense to talk about "simple to use" vs "simple to understand". You can talk about "easy to use" vs "easy to understand". The latter might mean "simple". But "simple" never means the former. And you never say it's "simple to X" for any value of X.
Under such a standard. The degree to which people are converging on this standard in actuality is not clear to me.
It's really not an arbitrary definition if you think about it etymologically. Very freely sim-ple is "unfolded" (c.f. pliers, plier qc). Whereas com-plex is "folded together". "Easy" relates more to a state of ignorance. It's about the user of the thing, not about the thing itself. (NB I'm just making this up. I'm not a linguist so I might be wrong).
And it's not only the caller, but also the caller of the caller, and so on, who needs to be prepared for exceptions. This all has severe ramifications on the code structure.
> There's no need to be dismissive. I refer to simple as simple to use, not simple to conceptualise.
Sorry. I need to control myself. It's not about you. As another commenter stated there is an agreed upon distinction between simple and easy. The distinction is important especially among programmers, and what you described might be easy but it's not simple.
Erm, no. Some consequences: non-obvious control flow, RAII, constructors, move semantics, exception safety, efficiency, stack unwinding...
A better way to deal with the "problem" of unchecked error codes is to have the compiler check that something happens to the result value. This is what Chandler Carruth suggests.
An even better (but orthogonal) way is to structure the code so it does only one thing at a time, and does any one thing only in one place (ideally).