I appreciate John's points here. I don't necessarily agree with all of
them (there's a spectrum of opinions, and that's fine, though I find #3
especially compelling), but I think this illustrates something about the
exceptions debate that is easy to miss: the people who hate checked
exceptions are much noisier about it than those who don't, and so they
are likely to be fooled by confirmation bias since all the other loud
voices seem to agree with them. But I've found that there are a lot of
people out there who quietly think checked exceptions are ... pretty
OK. Maybe even a majority. They just don't want to engage in a big
debate, they'd rather get their work done.
On 11/12/2022 11:16 AM, John Hendrikx wrote:
I very much disagree with this position, so I will make a case for
checked exceptions:
1) Without checked exceptions, using exceptions for alternative
outcomes as part of the contract of an API would no longer be
possible. This is because adding a new exception to the API would no
longer be a breaking change. Documentation is not going to save you
here. Effectively, exceptions would have only two uses left (like in
other languages): fatal JVM breaking errors and to signal mistakes
that could have been prevented by the programmer. Using them to
signal unpreventable errors would be highly suspect and likely quickly
be considered a bad practice.
In other languages (but also in some Java API's that use runtime
exception to signal unpreventable errors) the compiler cannot help you
with these alternative outcomes as it isn't even required to specify
them as part of your API. It helps you with the return value, but not
for any other outcomes. These other outcomes will now have to guessed,
gleaned from the documentation (if it is up to date), or by doing
manual code mining (of the called function and all the functions it
calls). Worse, these outcomes can change in later releases without
any warning as they're non-breaking changes.
2) API's without checked exceptions will necessarily become more
cluttered than their counterparts with checked exceptions. They would
have to return wrappers or unions or special values to enforce proper
use. API design like this prevents easily deferring the handling of
rare occurrences higher up the call chain with standard idioms to keep
the current code path clean and to the point.
3) Removing checked exceptions now would break the **design** of many
current API's, making them dangerous to use without the checks and
balances that were present when they were designed.
4) Earlier design decisions to exclude checked exceptions from new
idioms and API's were a mistake that should be rectified not doubled
down upon. A large part of the momentum against checked exceptions
comes from the clumsy way that checked exceptions must be handled
while using these new features that were essentially incompatible with
existing Java. Even with the current status quo, it is possible to
design API's that do allow checked exceptions in combination with
functional programming, it is just cumbersome to design these API's
without a dedicated union type (for exceptions). They either support
only one checked exception or some limited number. Their use is
however as transparent as API's that do not support checked exceptions.
5) The fact that the value of checked exceptions is hard to recognize
or cumbersome for beginners or non-Java developers should not have any
bearing on language design.
Checked exceptions differentiate between the common preventable
programming errors and essential business logic. The difference
between a runtime exception (leaving out Error) and a checked
exception is that one can only occur in incorrect code, while the
other can occur even in correct code. One indicates a preventable
programming mistake and the other a missed path in your logic.
Throwing these on the same heap will effectively reduce the usefulness
of all exceptions to programming mistakes (and fatal errors) only.
6) If anything, I think there should be way to promote runtime
exceptions to checked exceptions within certain modules. This would
allow API / library designers the option to have the compiler check
their code for missed documentation and missing logic even for what
externally would be a runtime exception. This would be especially
useful during initial design and also later refactoring as the
incredibly useful compiler checks for checked exceptions could be
extended to proper use and propagation of runtime exceptions within
that module.
--John
------ Original Message ------
From "Remi Forax" <[email protected]>
To "Nathan Reynolds" <[email protected]>
Cc "amber-dev" <[email protected]>; "amber-spec-experts"
<[email protected]>; "Ron Pressler"
<[email protected]>
Date 12/11/2022 08:58:57
Subject Retiring Checked Exceptions Was: Throwing Functions
[promoted to amber-spec-experts]
I think we should tackle this problem at its core and retire the
notion of checked exceptions
The written are on the wall since quite some time, but we have not
yet acknowledge it.
Let's recap the issue and the clues pointing in that direction.
I believe the first straw can be tracked back to the inability of the
type system to create a type variable containing the union of
exceptions when generics where introduced in Java 5.
It's the reason why there is no ThrowableConsumer, as an example,
imagine that a stream can declare a checked exception Stream<T, E>
and a user call map() that takes a ThrowableFunction,
we need a way to express that the resulting stream is that combine
the exceptions of the original stream and the exceptions that may be
raised by calling the function of map.
Stream<T, E extends Exception> --> map(ThrowableFunction<T, R, F
extends Exception>) --> Stream<R, E | F>
Or perhaps from the beginning of Java, adding a checked exception to
a "throws" colors the function the same way the keyword async or Rust
lifetime type information color a function [1]. Adding a checked
exception to a function is not a backward compatible change.
So we have some of idioms that beginners need to internalize to try
workaround checked exceptions,
- The oldest is i think, Thread.currentThread().interrupt() which
allows to silence an InterruptedException at the price of continuing
to execute the code until the next blocking call
- UncheckedIOException and IOError that both wrap an IOException into
an unchecked exception allowing tunneling of checked exceptions ;
wrapping an IOException and unbundle it later.
- Unsafe.rethrow (this one was retired)
- the unfamous rethrow using erasure to see a checked exception as an
unchecked exception
static <T extends Throwable> AssertionError rethrow(Throwable
cause) throws T {
throw (T) cause;
}
- IntelliJ has changed the default try/catch snippet to instead of
calling printStackTrace() to throw a RuntimeException wrapping the
exception.
This simple change is i believe the best change to Java in the
recent years (perhaps toes to toes with records), at least now the
code of my students does not print the stack trace and resume its
course when an exception occurs.
try {
...
} catch(FooException e) {
throw new RuntimeException(e);
}
Also no language presented as potential successor of Java, neither
Scala nor Kotlin have checked exceptions, because functions with
checked exceptions do not compose.
If Java wants to be the next Java, we will have to drop checked
exceptions at some point.
I don't see the relevance of what other languages are doing with their
own agenda's. There is no need to drop checked exceptions, they just
need to be an integral part of the whole. Removing them doesn't make
Java the next Java, it just makes it yet another language with
slightly different syntax that succumbed to a vocal minority that
values simplicity over safety.
The good news is that seeing all exceptions as unchecked exceptions
is a backward compatible change, "throws" can still be supported for
documentation purpose, the compiler can emit a warning instead of an
error if there is no catch corresponding to a checked exception and
allow everyone to catch any exceptions in the code.
I think we should recognize that the idea of checked exceptions was a
good idea on paper but not a good one in practice and work to retire
the concept of checked exceptions from Java.
Rémi
[1]
https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
------------------------------------------------------------------------
*From: *"Nathan Reynolds" <[email protected]>
*To: *"amber-dev" <[email protected]>
*Sent: *Saturday, November 12, 2022 5:47:10 AM
*Subject: *Throwing Functions
I am sorry if I am very late to the discussion. Consumer,
Function, Predicate, and Supplier don't allow for throwing
checked exceptions. This feature is needed in many cases. What
about adding a variant that allows for throwing checked
exceptions? For example, a ThrowableConsumer could be defined as
such...
public interface ThrowableConsumer<T, E extends Throwable>
{
void accept(T t) throws E;
}
A method that receives this as a parameter could be defined as
such...
public <E extends Throwable> void process(ThrowableConsumer<T, E>
consume) throws E
{
...
}
The compiler takes care of ensuring the checked exception is
dealt with in the caller to process().