7 ms·
Unless you forget to catch the right type of exception. Then all hell breaks loose.
by lordofgibbons 2y ago
Unless you forget to catch the right type of exception. Then all hell breaks loose.
- deleted 2y ago[deleted]
- bigstrat2003 2y agoPeople bitch about checked exceptions in Java but this is precisely why I think they're a great idea. You can't forget to catch the right type of exception.
- lomnakkus 2y agoNo, but you can easily end up missing some because somebody wrapped them in some sub-type of RuntimeException because they were forced(!) to. This happens all the time because the variance on throws clauses it at odds with the variance of method signatures (well, implementations, really -- see below). A new implementation of a ThingDoer usually needs to do something more/different from a StandardThingDoer... and so may need to throw more types of exceptions. So you end up having to wrap exceptions ... but now they don't get caught by, say, catch(IOException exc). If you're lucky you own the ThingDoer interface, but now you have a different problem: It's only JDBCThingDoer which can throw SQLException, so why does code which only uses a StandardThingDoer (via the ThingDoer interface) need to concern itself with SQLException? Checked exceptions in Java are worse than useless -- they actively make things worse than if there were only unchecked exceptions. (Because they sometimes force the unavoidable wrapping -- which every place where exceptions are caught needs to deal with somehow... which no help from the standard "catch" syntax.)
- iainmerrick 2y agoOne thing you can do in Java is parameterise your interface on the exception type. That way, if the implementation finds it needs to handle some random exception, you can expose that through the interface -- e.g. "class JDBCThingDoer implements ThingDoer<SQLException>". Helper classes and functions can work with the generic type, e.g. "<E> ThingDoer<E> thingDoerLoggingWrapper(ThingDoer<E> impl)". I think this works really well to keep a codebase with checked exceptions tractable. I've always been surprised that I never saw it used very often. Anyone have any experience using that style? I guess it's not very relevant any more because checked exceptions are sadly out of fashion everywhere. I haven't done any serious Java for a while so I'm not on top of current trends there.
- lomnakkus 2y agoIn a former life I worked with a codebase that used that style. Let's just say it isn't enough.
- iainmerrick 2y agoCan you remember what sort of problems you were hitting?
- int_19h 2y agoHow do you handle the situation where the code might need to throw (pre-existing) exceptions that don't share a useful base class?
- iainmerrick 2y agoI don’t remember! Possibly that’s one of the cases where it doesn’t work out. Of course, if you had proper sum types, that situation wouldn’t be a problem.
- gf000 2y agoJava has proper sum types, but what one needs here is union types. They are not the same, sum types are labeled and are disjoint. You want `MyException | ThirdPartyException` here, though.
- iainmerrick 2y agoYou're right, I had it backwards! Thanks for the correction. Now I'm wondering, could that actually be all that's needed to rescue checked exceptions? Is there any language that has that combination of features?
- gf000 2y agoWell, scala has union types, but it doesn't do checked exceptions per say (but it does have a very advanced type system so similar structure can be easily encoded). I think checked exceptions is pretty rare, so I don't know.. probably some research language (but they often go the extra mile towards effect types)
- pjmlp 2y agoAdditional info, they predate Java, having made an appearance in CLU, Modula-3 and C++, before Java was invented. I miss them in other languages every time I need to track down an unhandled exception in a production server.
- Tempest1981 2y ago>> People bitch about checked exceptions > they predate Java, having made an appearance in CLU, Modula-3 and C++ Checked exceptions in C++? Can you force/require the call chain to catch an exception in C++? At compile time?
- pjmlp 2y agoThat was part of the idea behind them yes, as many things in WG21 design process, reality worked out differently, and they are no longer part of ISO C++ since C++17. Although some want to reuse the syntax for value type exceptions, if that proposal ever moves forward, which seems unlikely.
- Fire-Dragon-DoL 2y agoThe problem is that a checked exception makes sense only at a relatively high level of the app, but they are used extensively at a low level
- piva00 2y agoMy main gripe with checked exceptions is they create a whole other possible code path on each `catch` clause. I tend to keep checked exceptions to the absolute minimum where they actually make sense, all the rest are RuntimeExceptions that should bubble up the stack.
- LinXitoW 2y agoBut so would every single other method to react to different types of errors, no? In something like go, you're even required to create the separate code path for EVERY SINGLE erroring line, even if your intention is simply to bubble it up.
- KarlKode 2y agoThat's kind of how you do it in go. Either: 1. Bubble up error (as is/wrapped/different error. 2. Handle error & have a (possibly complex) new code path. There's also the panic/recover that sometimes is misused to emulate exceptions.
- layer8 2y agoThey don’t create any other code paths than RuntimeExceptions.
- evantbyrne 2y agoThat would be true if not for Java making the critical mistake of excluding RuntimeException from method definitions, so in-practice people just extend RuntimeException to keep their methods looking "clean".
- bbatha 2y agoOr are forced to because they want to use generics or lambdas.
- gf000 2y agoBoth work with checked exceptions.
- int_19h 2y agoThe problem is that there's no way to specify an exception specification like "I propagate everything that this lambda throws" (or, for generics, "that method M of class C throws").
- layer8 2y agoNo, but you can have an interface like interface Func<P, R, X extends Exception> { R func(P param) throws X; } or the same with more than one exception type, and convert your lambda to that. This works. The only problem is that you can’t abstract over an arbitrary number of exception types. In principle, one could imagine a syntax for variadic type parameters like interface Func<P..., R, X... extends Exception> { R func(P... params) throws X...; } that would solve that problem.
- erik_seaberg 2y agoWill the compiler infer that a lambda or a method ref implements Func with its exception type param, or do you have to rewrite call sites?
- LinXitoW 2y agoThe biggest issue with checked exceptions in modern Java is that even the Java makers themselves have abandoned them. They don't work well with any of the fancy features, like Streams. Checked Exceptions are nothing but errors as return values plus some syntactic sugar to support the most common response to errors, bubbling.
- gf000 2y ago> They don't work well with any of the fancy features, like Streams. Because that would require effect types, which is quite advanced/at a research level currently.
- layer8 2y agoAll it would require is more support for sum types and variadic type parameters, and maybe fix some hiccups in the existing type inference. You can already write a Stream-like API that supports up to a fixed number of exception types (it’s just a bit annoying to write). The main issue at present is that you can’t do it for an open-ended number of exception types and abstract over the concrete set of types.
- gf000 2y agoThe throws clause would require union types, not sum types though (you can observe it in the catch part of a try catch, e.g. `catch ExceptionA | ExceptionB`. But java can't support unions elsewhere, it will have to be replaced by the two exceptions' common supertype.
- layer8 2y agoI was subsuming union types under sum types here, maybe a bit imprecisely. The following already works in Java (and has for a long time): interface F<X extends Exception, Y extends Exception> { void f(int n) throws X, Y; } void g() throws IOException, SQLException { F<IOException, SQLException> f = n -> { if (n > 0) throw new IOException(); else throw new SQLException(); }; h(f, 0); } <X extends Exception, Y extends Exception> void h(F<X, Y> f, int n) throws X, Y { f.f(n); } We merely want for F and h to be able to work for any number of exception types. We don't need the ability to declare variables of type X | Y for that. Of course, it would be nice not having to write IOException, SQLException multiple times in g, and instead have some shortcut for it, but that's not strictly necessary. The main problem currently is that you have to define F1, F2, F3,... as well as h1, h2, h3,... to cover different numbers of exception types, instead of having just a single definition that would abstract over the number of exception types.
- deleted 2y ago[deleted]
- Yoric 2y agoIs it time to brag about Rust error-handling or should we wait a little?
- gf000 2y agoIt's certainly better than Go's (Go's is barely better than C's and that's quite a low bar), but I don't think that sum types are the global optimum. Exceptions are arguably better from certain aspects, e.g. defaulting to bubbling up, covering as small or wide range as needed (via try-catch blocks), and auto-unwrapping without plus syntax. So when languages with proper effect types come into mainstream we might reach a higher optimum.
- LinXitoW 2y agoMaybe I'm too pessimistic, but Rust style error handling feels like the global optimum under the constraint that the average developer understand it. Go is a language that exists purely because people saw Monads in the horizon and, in their panic, went back to monke, programming wise. Rust error handling is something that even many Go fans have said is a good abstraction.
- Yoric 2y agoNo, sum types are certainly not a global optimum. But they remain the best error-handling mechanism that I've used professionally so far. Effect types (and effect handlers) are very nice, but they come with their own complexities. We'll see if some mainstream language manages to make them popular.
- KingOfCoders 2y agoAfter a decade of Scala and Rust I no longer believe in monads and prefer the way Go does error handling. for a <- a() b <- b() { return a + b } looks nice but only by hiding error handling. Today I like looking at code and see the error handling. With monads you end up with monad stacks and transformers which introduce their own failure states.
- 2y ago
- xienze 2y agoYou may be thinking a bit too much about what happens in _Go_ when you forget to check for an error response from a function -- the current function continues on with (probably) incorrect/nil values being fed to subsequent code. In Java when an uncaught exception is thrown, the exception makes its way back up the call stack until it's finally caught, meaning subsequent code is _not_ executed. It's actually a very orderly termination. In any Java web framework (Spring et al) there's always a centralized point at which exceptions are caught and either built-in or user-specified code is used to translate the error to an HTTP response. This makes for much more pleasant code that is mostly only concerned with the happy path, e.g., my REST endpoint doesn't have to care if an exception is thrown from the DAO layer as the REST endpoint will simply terminate right then and there and the framework will map the exception to a 500 error. Why anyone would prefer Go's `if err != nil {}` error handling that must be added All. Over. The. Place. at every single level of the application is beyond me.
- LinXitoW 2y agoMy slightly snarky take is that liking Go is simply a defensive reaction to one too many AbstractFactoryBeanFactory. Too many abstractions overloaded their "abstraction-insulin", so now they can only handle minute amounts of abstraction.
- gf000 2y agoI liked your other comment's take with the monads better :P
- LinXitoW 2y agoWhich Go doesn't fix either, because their errors are all just "error", aka you can also forget to catch the right type of error. If only there was a way to combine optimizing the default path (bubbling), and still provide information on what errors exactly could happen. Something like a "?" operator and a Result monad...