Hi all, toaday I decided to talk about an old argument in the Java community: the dreaded Checked vs Unchecked exceptions. For those of you who have never heard of this, here is a brief recap:
- Checked Exceptions: when calling a method that throws a checked exception the compiler will force you to either handle it with a try-catch block or add it to the signature of your method
- Unchecked Exception: they extend the RuntimeException class and if not handled simply bubble up in your calls stack.
The dispute on their usage has been controversial, with checked exceptions usually regarded as the black sheep. Arguments against checked exception that you could have heard are:
Checked exceptions can be ignored by swallowing them, so what’s the point of having them?
try {
// do stuff
} catch (AnnoyingCheckedException e) {
// do nothing
}
Checked exceptions are easy to ignore by re-throwing them as RuntimeException instances, so what’s the point of having them?
try {
// do stuff
} catch (AnnoyingcheckedException e) {
throw new RuntimeException(e);
}
Checked exceptions result in multiple throws clause declarations. The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app.
As far as I can tell, in the Java world the following best practice has been established:
- use checked exception for public API
- use
RuntimeExceptions
for your internal modules.
The reason being that checked exceptions clutter your code (so avoid them inside your module) but document the external behaviour of your API. The point I want to make here is that:
RuntimeExceptions
are evil
If you feel offended by this argument you’re free to stop reading and go out for a walk, they say it’s really good
for your health! Why are they evil? Well because whenever you’re throwing a RuntimeException
from one method
you’re telling a lie. Which lie? Say you have a method like:
public Money from(BigDecimal amount, String currency) {
if (amount.signum() < 0) {
throw new ValidationException("whatever");
}
return new Money(amount, currency);
}
This method is lying! It is telling its user it will return an instance of Money
given a BigDecimal
and a
String
, but it is not, or at least not always. You may say: I can always document this behaviour with a test, but
still that is a loose end. Another example might be:
interface Repository<T> {
T save(T elem);
}
You know from your experience that the save
method might throw an exception, but you have been told not to use
checked exceptions, so you are failing to document it. Another couple of considerations:
- given their ability to bubble up in the calls stack,
RuntimeExceptions
are nothing else than glorified GOTOs - methods that throw
RuntimeExceptions
are more difficult to reason about because you have to keep that information in the back of your mind - it is too easy not to acknowledge a
RuntimeException
has been thrown
Checked exceptions, on the other hand, by forcing you to handle them are more honest, and they are actually telling you that the method you are calling returns 2 possible values:
- a value of its return type
- or a value of the checked exception type it throws
By now I guess you might be wondering where I left the FP glasses I mentioned in the title: here they come. In the
FP world (disclaimer: I am just at the beginning of my journey in this world) there is a type meant to be used to
express exactly the choice between two types: Either<E, A>
where:
- A is the right type (got the hint?), i.e. the type you would have ideally returned
- E is the type of the possible error that may occur
Aside from being exactly what you might have been looking for to explicitly state what your method returns, Either is also a monadic data type. Without going into the rabbit hole of talking about Monads, let’s say a monadic data type exposes 2 functions that make it easy to work with the right value once you get an Either into your hands. The 2 methods are:
- map that takes a function
f: A -> B
and will turn yourEither<E, A>
into anEither<E, B>
- flatmap that takes a function
f: A -> Either<E, B>
and will still turn yourEither<E, A>
into anEither<E, B>
You might be familiar with this 2 new functions because the new Stream API (since java 8) or Optional (again
starting from Java 8) support those 2 functions. And you may appreciate how they promote a more declarative way of
programming. Either
instead is not in the Java JDK, but you can find it in (not an exhaustive list):
- VAVR, a functional extension library for Java (https://www.vavr.io/)
- Arrow, a functional companion library for Kotlin, but you can use it also in your Java code if you wish (https://arrow-kt.io/)
- the Scala lang itself (https://www.scala-lang.org/).
Conclusion
The FP way of thinking about functions as mapping between inputs and outputs (and nothing more) brought me to the conclusion that you should always
Return a value from your function or not return it (maybe the parallel does not work here). There is no try!
Or, to put it not in Yoda’s terms,
A function should be total and always return a value!
Well that’s all folks. Hope you have enjoyed it!