lastminute.com logo

Technology

Checked vs unchecked exception (with FP glasses)

angelo_sciarra
angelo sciarra

The dark side of Unchecked Exceptions


alt Do or do not, there is no try
Do or do not there is no try

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 your Either<E, A> into an Either<E, B>
  • flatmap that takes a function f: A -> Either<E, B> and will still turn your Either<E, A> into an Either<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):

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!


Read next

SwiftUI and the Text concatenations super powers

SwiftUI and the Text concatenations super powers

fabrizio_duroni
fabrizio duroni
marco_de_lucchi
marco de lucchi

Do you need a way to compose beautiful text with images and custom font like you are used with Attributed String. The Text component has everything we need to create some sort of 'attributed text' directly in SwiftUI. Let's go!!! [...]

A Monorepo Experiment: reuniting a JVM-based codebase

A Monorepo Experiment: reuniting a JVM-based codebase

luigi_noto
luigi noto

Continuing the Monorepo exploration series, we’ll see in action a real-life example of a monorepo for JVM-based languages, implemented with Maven, that runs in continuous integration. The experiment of reuniting a codebase of ~700K lines of code from many projects and shared libraries, into a single repository. [...]