5 ms·
Exception based error handling is unsafe when they are unchecked exceptions. Checked exceptions however are as safe as Either, Try, Monads, Applicatives or what
by misja111 3y ago
Exception based error handling is unsafe when they are unchecked exceptions. Checked exceptions however are as safe as Either, Try, Monads, Applicatives or whatever.
You are forced to declare them in your method signature, the caller is forced to either handle them or rethrow them + declare them as well.
And I guess this is precisely why so many developers hate them; they don't like the extra work they have to do to catch all those edge conditions.
This is why we see so many empty catch blocks or upcasting to Exception or even Throwable; it is laziness.
I would also argue that checked exceptions are no more complex than Eithers, Try or Applicatives. Actually passing Eithers or Applicatives around everywhere can easily clutter your code as well, IMO it can be worse than checked Exceptions.
- renewiltord 3y agoUltimately, core language features that make things ergonomic enough for lazy developers to get it right will result in better software. And ones that are so unergonomic that only industrious developers will get them right will result in worse software. Modern languages allow these to be zero cost abstractions so there is little tradeoff.
- misja111 3y agoI have been coding in Scala/ Haskell for the last 10 years, before that 15 years in Java. What I'm seeing is that there are as many lazy developers in FP as there are in Java, maybe even more. And despite all the nice safeguards that FP provides, there are still plenty of ways for lazy devs to work around them. For instance the IO monad, which is used everywhere Scala/Cats. It can contain a result or an error. If you don't feel like checking for an error after you called some method, you can just pass it up and return the IO from your method. Does that sound familiar? It behaves just like a checked exception, the only difference is that methods don't need to declare any error or exception in their IO signature.
- mrkeen 3y ago> It behaves just like a checked exception, the only difference is that methods don't need to declare any error or exception in their IO signature. Then it behaves like an unchecked exception. Which is fine. 'Checking' is the act of modifying your source code to declare the error.
- bedobi 3y ago> Checked exceptions however are as safe No. If I add a checked UserNotFound exception to a getUser db call, you can bet someone higher up the stack will do try catch Exception e, so now they're catching OutOfMemory and who knows what else.
- vips7L 3y agoOutOfMemoryError isn't an exception so they would not be catching it.
- bedobi 3y agowhatever, the point is exception allows and encourages people to catch less and more than they should
- vips7L 3y agoSometimes top level code needs to do this. It's not always wrong. It's not always black and white in programming.
- bedobi 3y ago> Sometimes top level code needs to do this come on, it's not as if anyone disagrees with that, but that's extremely, extremely rare, overwhelmingly, callers are just dealing things like with UserNotFoundException, NullPointerException, what have you, and there's no reason why the compiler should happily let you catch Exception (or nothing) when it could just be giving you an honest object back
- wewtyflakes 3y agoIt has not been extremely/overwhelmingly rare in my experience. Further still, within the context of Java, exceptions are objects.
- twelve40 3y ago
- skissane 3y ago> And I guess this is precisely why so many developers hate them; they don't like the extra work they have to do to catch all those edge conditions I hate checked exceptions when they force me to handle an exception which I know is impossible given the arguments passed to the method. For example, some older Java APIs take an encoding name and force you to handle the checked UnsupportedEncodingException - even when the encoding name was something like “US-ASCII” or “UTF-8” which is required to exist by the Java standard, and if somehow they didn’t there is often no possible error recovery than just crashing. This has been fixed in newer versions by introducing new APIs which throw an unchecked exception instead, and also by introducing constant objects for the standard charsets
- Supermancho 3y ago> I hate checked exceptions when they force me to handle an exception which I know is impossible given the arguments passed to the method. If I want to ignore a set of exceptions, I have the option to catch(Exception e) {} signaling that I recognize the risks that have been explicitly communicated by the API (that throws). An @IgnoreExceptions annotation would help dump the 5? boilerplate lines. The unknown risks for other non-specific Runtime exceptions, are not included. I can catch those too if I add a catch(RuntimeException e){}, again signalizing that I recognize the risks such that other developers understand that I'm opting out of handling those conditions, which may or may not be errors in a classic sense. eg an expected socket not being available causing an IOException, because I'm doing some concurrent process.
- skissane 3y agoI've seen checked exceptions cause numerous bugs. Why? Because they encourage you to put catch clauses everywhere, and a lot of developers don't know how to write them properly (and even the best developers sometimes make mistakes that slip through the cracks). A common result of this is that a bug causes an exception, but the catch clause loses information about the original exception (such as by logging it without the stack trace, or throwing a new exception without setting the cause). Or else the catch clause just ignores the exception, and then you get some other error later (e.g. NPE because some field was unexpectedly null), but again you've lost the info on what the root cause problem is. If your code base contains 100s of catch clauses, that's 100s of opportunities for buggy catch clauses to exist. And even if you eradicate them all from your own code base, then you might get hit by one buried in a third party library. Without checked exceptions, you end up with far fewer catch clauses, and hence far fewer opportunities for buggy catch clauses. I think in most apps, most of the time, you just want to bubble all exceptions up to a central point which handles them – for example, in a web server, you can have a single catch clause for the whole HTTP request. If need be, you can make that central point extensible with custom handling with specific exceptions (like JAX-RS ExceptionMapper, or @ExceptionHandler beans in Spring – which you can then inject using IoC/DI). Once you've got that working, if you need catch clauses deeper in the code (e.g to return an error code to the frontend when the client sends bad data instead of just failing the request with a `NumberFormatException`), you can add them where appropriate – but unlike checked exceptions, you aren't forced to add them where you don't need them.
- lmm 3y ago> And I guess this is precisely why so many developers hate them; they don't like the extra work they have to do to catch all those edge conditions. This is why we see so many empty catch blocks or upcasting to Exception or even Throwable; it is laziness. Good developers should be lazy. Checked exceptions require you to do a bunch of cumbersome ceremony that makes your code unreadable, for what should be (and is, with Either or equivalent) at most a couple of symbols; no wonder developers hate that. > I would also argue that checked exceptions are no more complex than Eithers, Try or Applicatives. You'd be wrong. Checked exceptions as implemented in Java require two keywords of their own (throw and catch), a special change to method signatures (throws), a unique new kind of expression for multi-catch (|), a unique new kind of type for things caught in multi-catches, and special support in every other new language feature (e.g. Futures have a whole bunch of extra code to deal with exceptions). Eithers can literally be a regular class that you could write yourself using ordinary language features. A small piece of syntax sugar (something like "do notation", "for/yield comprehensions", or "?") is a good idea, but not essential, and if you do it right then you can implement that once across your whole language (for applicatives/monads in general) and use it for eithers, futures, database transactions, audit logs, almost anything. https://philipnilsson.github.io/Badness10k/escaping-hell-with-monads/ https://philipnilsson.github.io/Badness10k/escaping-hell-wit... .
- hedora 3y agoChecked (and unchecked) exceptions create an exponential number of control flow paths. Consider: try { throws_a(); r = grab_resource(); throws_b(); r.throws_a(); } catch (a) { r.release(); // oops; null pointer some times } catch (b) { try { r.release(); } catch (a) { // nooo.... } } finally { if r != null { r.finalize() // use after release (sometimes) r.release() // ??? } } There is plenty of academic literature showing that real programs are even worse than my contrived example, on average.
- Supermancho 3y agoThe construction of arbitrary convolutions of logic is not demonstrative of your assertions. A multitude of code paths is not specific to checked exceptions. Note: It is possible to try catch a union of exception types in the recent versions of Java (catch X | Y | Z). The paths are still there if the exceptions are all unchecked or combined or not using exceptions at all. Passing exceptions up is rarely the right choice, imo. If it's a terminal runtime, sure, obv.
- josefx 3y ago> r.release(); // oops; null pointer some times Quite sure Java would prevent that if r was never assigned. Also most languages provide cleaner try-with-resources or RAII style lifetime management so you don't actually end up with that kind of spaghetti code unless you actively go out of your way to be bad at programming.
- masklinn 3y ago> Also most languages provide cleaner try-with-resources or RAII style lifetime management so you don't actually end up with that kind of spaghetti code unless you actively go out of your way to be bad at programming. Context managers (try with resources) specifically don't work at all when release is conditional e.g. open a file, need some post processing, need to close the file if the post-processing fails but return it if it succeeds. Defers don't either, unless you have a specialised one (e.g. zig's errdefer). Actual RAII (aka affine types) does work, but adding that to a GC'd langage after the fact is a huge undertaking and increase in complexity, because the interaction between affine and normal types is fraught.
- nradov 3y agoI don't hate checked exceptions because they make me do extra work, I hate them because they inhibit the proper use of inheritance for OO design.