3 ms·
I think we have compatible views. Each layer of the software must decide it's requirements and handle errors appropriately per requirements. You're right I didn
by dcsommer 4y ago
I think we have compatible views. Each layer of the software must decide it's requirements and handle errors appropriately per requirements. You're right I didn't articulate when to handle an issue locally vs. pass it up. I think that's where requirements (and also explicit API guarantees) come into play.
I do think that APIs that "overpromise" by not returning the errors they do not handle to the caller, and instead halt or throw an exception, do their users a disservice in the long-run. These just become undocumented cases that bite you later on. Better libraries have all these conditions baked into the API itself.
- tsimionescu 4y agoBut an exception is a part of the API, why are you putting it at the same level as halting?
- dcsommer 4y agoIn my experience, APIs that throw rarely define all the exceptions that can come from it, especially transitively. I see exceptions as a failed (because undocumented, but still important for correctness) attempt at compromising between halting and returning an error.
- hinkley 4y agoI wonder if there's a moral equivalent of borrow semantics where we more formally define error propagation.
- enticingturtle 4y agoJava does this with checked exceptions and it’s a huge hassle to work with.
- tsimionescu 4y agoTo be fair, there are two reasons why it's considered a hassle. One reason, which is a bad reason, is that many people just don't like to document and handle errors. Instead of being happy that the compiler is telling them that they forgot to handle or declare an IOException from this call, they get annoyed that it's yelling at them "for no reason". This is simply lack of understanding/care for how you program. The other reason, which is actually a problem with the language that could be fixed, is that Java doesn't allow functions to be polymorphic in their Exception types, like it does for argument and return types. This makes higher-order constructs very annoying - for example, `stream.map(Function)` should `throw` the same Exceptions that `function` throws, as should `Arrays.sort(array, Comparator)`. Without this capability, you end up with an extremely ugly and brittle pattern of doing: //V foo(T x) throws SomeException(); Stream<V> bar(Stream<T> stream) throws SomeException { try { return stream.map( (x) -> { try { return foo(x); } catch (SomeException e) { throw new RuntimeException(e); }); } catch (RuntimeException e) { if (e.getCause() instanceof SomeException) { throw (SomeException)e.cause(); } else { throw e; } } } When all you wanted was: Stream<V> bar(Stream<T> stream) throws SomeException { return stream.map(Foos::foo); } This huge verbosity is obviously unnecessary and ugly, and could be removed with some compiler support (compiler could just insert this), or some more complex runtime support. In my opinion, if Java did this, it would actually have the best error handling mechanism of any language on the market - much better than Haskell or Rust.
- Tainnor 4y ago> One reason, which is a bad reason, is that many people just don't like to document and handle errors. Instead of being happy that the compiler is telling them that they forgot to handle or declare an IOException from this call, they get annoyed that it's yelling at them "for no reason". This is simply lack of understanding/care for how you program. Usually, what happens is that you call some library code (or some code that a teammate wrote) and that code will declare an IOException or something like that. In many cases, there's no point in handling that, as that file that you're trying to open or similar isn't supplied by the user but e.g. a static resource. That's the entire point of the article, that panicking when encountering a violated precondition is totally acceptable. The unchecked vs. checked exception distinction also suffers from the fact that it's usually the call site, and not the declaration site, that knows whether an exception is recoverable or not. Java checked exceptions would be fine if it had something like "unwrap", which just converts a checked exception to an unchecked one, but it doesn't, and that's why everyone kind of hates them.
- girvo 4y agoIt doesn’t have to be. Nim’s checked exception-like “raises” pragma is quite lovely to work with.
- tsimionescu 4y agoYou can still, at the very least, `catch(Exception e)` or `catch(...)` to handle a failure. Odds are very good in my experience that you will anyway have nothing better to do than log the error and abort the higher-level operation (e.g. HTTP request handler), even if you do know the specific type of exception that happened. Also, even in languages that have error return types/codes, it's very uncommon to see anything other than the most generic error value/return code allowed by the convention (e.g. `return -1` in C or `return fmt.Errorf("...")` in Go). Writing an API to document all possible failure modes is hard, and is rarely done, regardless of the mechanics of how errors are returned. One of the exceptions I've often seen is in SQL APIs, which generally do need to report the specific SQL errors that were signaled. And here, I've seen all possible errors explicitly exposed in the API, typically through a rich Error or Exception type that has a field for the specific SQL error code. Not to mention, we were mostly discussing cases where a library finds itself in a bug situation, say a null pointer case. I would not expect the API to express the possibility of "NullPointerException" or "ArrayIndexOutOfBounds" as possible return values, but I do want the language to raise these and allow me to decide how to handle them instead of simply halting the entire program - at least in managed memory languages where memory corruption is not possible/likely (if there is a good chance of memory corruption, like in C++, halting is indeed much better than raising an exception).
- int_19h 4y agoWhy would you ever want to handle a null pointer or index out-of-bounds error from a code that's not yours (i.e. basically a black box)?
- hinkley 4y ago> You're right I didn't articulate when to handle an issue locally vs. pass it up. I'm saying this is an everybody problem. Most of us just pass the buck by default. It can take quite a bit of cajoling to get error passing pushed back down to the layer that can best cope with it, once it has escaped up the stack. And when we split teams horizontally, instead of vertically, this happens practically all the time. This is one of the primary reasons I push for feature teams instead of client/server teams. The distance between, "I think this is done" and "this is ready to go to production" is mercifully small, instead of unknowable.