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.)
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.
Of course, if you had proper sum types, that situation wouldn’t be a problem.
You want `MyException | ThirdPartyException` here, though.
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?
Unfortunately, this take on lambdas was deemed too complicated, and so we got the present system which doesn't really try to deal with this use case at all.
I miss them in other languages every time I need to track down an unhandled exception in a production server.
> 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?
Although some want to reuse the syntax for value type exceptions, if that proposal ever moves forward, which seems unlikely.
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.
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.
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.Checked Exceptions are nothing but errors as return values plus some syntactic sugar to support the most common response to errors, bubbling.
Because that would require effect types, which is quite advanced/at a research level currently.
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.
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.
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.
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.
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.
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.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...