Programming Chi

Tomasz Fijałkowski's programming blog.

Don't misuse unchecked exceptions

In its early days, Java has a wide uses of checked exceptions. However, lazy nature of programmers triggered trend to abandonment checked exceptions to unchecked exceptions. This approach causes negligence an error handling and hiding side effects. Additionally exceptions catching is not checked at compilation level.

Lazy programmers don’t read a documentation, in particular information about throwing unchecked exceptions. Usually the way of errors handling is limited to catching Exception or RuntimeException on the controller tier. Handling of specific exceptions is added in the proper positions when failure cases are detected on QA or prod env.

However, there is nothing exceptional in IO failure or invalid input received from user. In such situations an error is commonly a successful result.

In practice

Unfortunately, my team last case was much more complex. The whole code based on CompletableFuture which can be completed exceptionally. This caused a horrible double error handling, like e.g.

CompletableFuture<Content> render() {
    try {
        return doSomthing()
            .exceptionally(ex -> handleError(ex));
    } catch (Exception ex) {
        return CompletableFuture.completedFuture(handleError(ex));
    }
}

As if that was not enough, some methods had comments like

/**
 * never returns a CompletedExceptionally CF
 */

What should be read as “never returns a CompletedExceptionally CF cause by business problem” because there can still be an error like OutOfMemoryError and there’s nothing you can do about it. As I mentioned previously, programmers don’t read documentation. Such code comments didn’t prevent appearance a redundant error handling using exceptionally, which couldn’t be called.

Last but not least, the majority of Java 8 functional interfaces like Supplier, Function, BiFunction etc. don’t allow to throw checked exceptions. Because of that we couldn’t use that approach with CompletableFuture.

Refactoring

To improve the quality of our life we refactored the code in two steps. The first was very simple - we introduced the rule that method returned CompletableFuture doesn’t throw any exception - the exception are caught as early as possible and mapped to failure future. At the second step we derived the patterns of other languages.

Functional programming treats successful execution of an operation on a par with errors. In Scala to show the possibility of occurring error, functions may return Either and Try classes from standard library or \/ and Validation from scalaz lib. A similar approach is in go lang. The convention says that the last element of a tuple returned by a function contains an information about the error.

We decided to use similar class to \\/ and named it Result as a result of some operation (which can be either successful or failed). The sample code of this class is available on my github - Result.

In a new version of the code, exceptions are thrown only when invariants are broken, which means that there is a bug in the code.

Conclusion

Replacing the classic throwing unchecked exceptions, by returning type that explicitly informs whether an error has occurred, allowed us to improve a readability of the code and reduce a number of errors. It also facilitated a code refactoring.