Hi all,
for my first post/article/whatever here, I have 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!
Top comments (7)
Great article, thank you for taking the time to write it.
This is a difficult topic and as you mention always leads to questions about which to use. I have also heard this strategy:
Use an unchecked exception if you cannot recover, use a checked exception if you can. This is quite complicated to me because you then must decide for your consumer what is fatal and what is not, and the answer is likely not the same for all. Along with that, if something is recoverable or expected, an exception may be the wrong way to go.
Have you heard of this take before? Curious on your thoughts here.
Hello, thanks for the appreciation.
Well, to be honest, I have never heard of this perspective on the subject.
Let's say that I understand the point on checked exception, it's mine too, I can also see the point on unchecked even if, as you say, it's difficult to decide on behalf of your consumer on what is fatal or not and I would also argue that if an event is indeed fatal it should modelled as an Error more than a RuntimeException (given it's fatal nature), leading again to the conclusion that RuntimeExceptions are in the end GOTOs in disguise.
Thanks for the response. I agree an error could be better than an exception there, and also gives the consumer more control. Determining behavior for not only what you control, but also what your consumers control makes developing anything meant to be for generic use difficult.
Thanks again and great post!
Thanks for this. It reminds me of Yegor B. "Checked-vs-unchecked" (yegor256.com/2015/07/28/checked-vs...).
I don't like the proposal of using functional to "fix the problem".
In my experience, you could use unchecked exceptions to "represent" technical issues and checked exceptions to "represent" business issues, but I'm pretty sure that even with this principle in mind we'll have questionable situations...
And maybe this is the problem: the constant search for a "rule", instead of accepting just the "principle" and allowing developers to handle that specific "situation"... freely.
Hi Roberto, thanks for your point of view.
I didn't know of Yegor article and I will go to read it ASAP.
My point was not to make another rule but to invite everybody to think about the reason why they are choosing one kind of exception over the other.
As long as you (rhetorical you) know why you are doing something, to me it's ok, even if I don't agree.
Interesting article, I recently faced this runtime exception dilemma in Kotlin and decided to use this library kotlinresources.com/library/kotlin...
P.S. Nice background image ;)
Thanks, I didn't know about it (I knew there was a Result in the standard lib but it was not so refined).