/ SOFTWARE DESIGN

On exceptions

When Java came out some decades ago, it was pretty innovative at the time. In particular, its exception handling mechanism was a great improvement over previous C/C++. For example, in order to read from the file, there could be a lot of exceptions happening: the file can be absent, it can be read-only, etc.<!--more-→

The associated Java-like pseudo-code would be akin to:

File file = new File("/path");

if (!file.exists) {
    System.out.println("File doesn't exist");
} else if (!file.canRead()) {
    System.out.println("File cannot be read");
} else {
    // Finally read the file
    // Depending on the language
    // This could span seveal lines
}

The idea behind separated try catch blocks was to separate between business code and exception-handling code.

try {
    File file = new File("/path");
    // Finally read the file
    // Depending on the language
    // This could span seveal lines
} catch (FileNotFoundException e) {
    System.out.println("File doesn't exist");
} catch (FileNotReadableException e) {
    System.out.println("File cannot be read");
}

Of course, the above code is useless standalone. It probably makes up the body of a dedicated method for reading files.

public String readFile(String path) {
    if (!file.exists) {
        return null;
    } else if (!file.canRead()) {
        return null;
    } else {
        // Finally read the file
        // Depending on the language
        // This could span seveal lines
        return content;
    }
}

One of the problem with the above catch blocks is that they return null. Hence:

  1. Calling code needs to check everytime for null values
  2. There’s no way to know whether the file was not found, or if it was not readable.

Using a more functional approach fixes the first issue and hence allows methods to be composed.:

public Optional<String> readFile(String path) {
    if (!file.exists) {
        return Optional.empty();
    } else if (!file.canRead()) {
        return Optional.empty();
    } else {
        // Finally read the file
        // Depending on the language
        // This could span seveal lines
        return Optional.of(content);
    }
}

Sadly, it changes nothing about the second problem. Followers of a purely functional approach would probably discard the previous snippet in favor of something like that:

public Either<String, Failure> readFile(String path) {
    if (!file.exists) {
        return Either.right(new FileNotFoundFailure(path));
    } else if (!file.canRead()) {
        return Either.right(new FileNotReadableFailure(path));
    } else {
        // Finally read the file
        // Depending on the language
        // This could span seveal lines
        return Either.left(content);
    }
}

And presto, there’s a nice improvement over the previous code. It’s now more meaningful, as it tells exactly why it failed (if it does), thanks to the right part of the return value.

Unfortunately, one issue remains, and not a small one. What about the calling code? It would need to handle the failure. Or more probably, let the calling code handle it, and so on, and so forth, up to the topmost code. For me, that makes it impossible to consider the exception-free functional approach an improvement.

For example, this is what happens in Go:

    items, err := todo.ReadItems(file)
    if err != nil {
        fmt.Errorf("%v", err)
    }

This is pretty fine if the code ends here. But otherwise, err has to be passed to the calling code, and all the way up, as described above. Of course, there’s the panic keyword, but it seems it’s not the preferred way to handle exceptions.

The strangest part is this is exactly what people complain about with Java’s checked exceptions: it’s necessary to handle them at the exact location where they appear and the method signature has to be changed accordingly.

For this reason, I’m all in favor of unchecked exceptions. The only downside of those is that they break purely functional programming - throwing exceptions is considered a side-effect. Unless you’re working with a purely functional approach, there is no incentive to avoid unchecked exceptions.

Moreover, languages and frameworks may provide hooks to handle exceptions at the topmost level. For example, on the JVM, they include:

  • In the JDK, Thread.setDefaultUncaughtExceptionHandler()
  • In Vaadin, VaadinSession.setErrorHandler()
  • In Spring MVC, @ExceptionHandler
  • etc.

This way, you can let your exceptions bubble up to the place they can be handled in the way they should. Embrace (unchecked) exceptions!

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
On exceptions
Share this