Dysfunctional programming in Java 5 : No Exceptions

From dysfunctional to functional

John McClean
Nov 1, 2018 · 11 min read

So far in this series, of functional programming in Java Tutorials, we’ve introduced 2 constraints that will significantly improve the quality of our code and now we introduce a third radical rule.

  1. 🏔Make your data model immutable 🏔
  2. 🚫Do not permit null assignments🚫
  3. 🚫🆕Do not throw Exceptions🆕🚫
Photo by Noah Buscher on Unsplash

Banning the throwing of Exceptions in your own code, will lead to better designs by forcing engineers to create Object models that ensure illegal states aren’t representable in code. There are parts of our applications that deal with tasks that can neccessarily fail (I/O to external systems) and there we can encode error states neatly as values, rather than exceptional disruptions to program control flow.

(⬇️️ Continued below ️⬇️️)

Outline

  • Why consider banning Exceptions?
  • The simplicity of pure functions
  • Converting methods that throw Exceptions to pure form
  • Integrating with code that throws Exceptions (via Try)
  • Managing I/O classes that throw Exceptions and require Resources to be closed

Why Ban Exceptions?

Exceptions increase the complexity of our code. In Java they can either be visible on the signatures of the methods we call (CheckedExceptions) or entirely invisible. Each style presents their own issues.

Exceptions disrupt the flow of execution in our applications, jumping from the point at which the exception is thrown to whatever point, with whatever state, our application defines the catch point (if it defines the catchpoint 😮).

We paraphrased Tolstoy in our article on Immutability :

Good designs are all alike; every bad design is bad in its own way.

GOTO (& error management via control flow) considered harmful

so many choices! silently swallowing Exceptions — check, throwing Exception — check, catching Exception — check, the list goes on!

Handling Exceptions is not easy, Oracle have dedicated a blog entry to the wrong approaches to Exception handling, that runs to around 15 antipatterns.

We can probably distill those 15 antipatterns into a smaller set of categories and add some new ones of our own — critical Exception related mistakes include

  • Failing to handle error states
  • Failing to anticipate error states
  • Hiding error states (e.g. catch Exception)
  • Complecting control flow

Even the relatively simple task of loading a file from disk can be fraught with danger — in the code below, if we haven’t taken the step of banning null assignments, catching Exception can hide errors caused if file hasn’t been initialized correctly (such as an NPE).

Alternatively if we catch too specific an Exception type, our application flow may jump unexpectedly on error

the compiler doesn’t enforce the handling of RuntimeExceptions

We face a Hobson’s choice in Java of dropping compiler help for managing Exceptions altogether (via RuntimeExceptions) or littering our APIs with throws XXX and attempting limit the specifity of the CheckedExceptions declared (to avoid error hiding). Neither approach is easy to get right in practice.

All of the challenges we face in Java around error handling stem from the fact that we handle Exceptional state by disrupting the flow of control, perhaps after running away in horror from the C++ practice of using error codes (😱😱😱😱)

Photo by Daniel Palma on Unsplash

Testing code that throws Exceptions

[Functional Concept] Pure Functions : the simplest functions

Function<Integer> addOne = a -> a+1;

Our Function addOne above always give the same answer for each input value.

int two = addOne.apply(1); //always 2
int three = addOne.apply(2); //always 3

This property of pure functions makes them really easy to test, to derive performance benefits from caching, and to reason about in our code. In Java addOne could just as easily be a method and still be a pure function

public int addOne(int a){
return a+1;
} //still a pure function

It is pretty easy to imagine a short but effective suite of unit tests for this method.

APIs that support illegal states are more complicated

public int addOneImpure(Integer a){
if(a==null)
throw InvalidArgumentException("Argument to addOne can not be null");
return a+1;
}

The key difference to note between the pure addOne and addOneImpure, is that addOneImpure accepts a data type that allows an Exceptional state (it is nullable) to be represented in our code. There are a number of steps we can take to mitigate this

  1. Developer discipline : This is part of the message of Dysfunctional programming in Java 4 : No nulls allowed (🤔). If we ban null assignments and enforce it rigorously with code reviews this exception should never be thrown.
  2. Design our APIs such that our input parameters only reflect permissable states (e.g. in this case by using the non-nullable int type, Java enums, custom case / data classes (roll on Future Java version! — for now roll your own with Lombok / Immutables) are very useful for this too. (This is also part of the message of Dysfunctional programming in Java 4 : No nulls allowed — null fields are often a sign our Object model needs refactoring 🙇🏽‍♀️)
  3. Use the return type to capture that no result could be computed in some manner, perhaps providing feedback on why. (Oh — we covered this too in Dysfunctional programming in Java 4 : No nulls allowed — I’m starting to think the underlying prinicples might be related in some way (🤔🙇🏽‍♀️😳)

Let’s examine Option 2+3 above — and figure out how can we refactor a method that throws an Exception into an pure one that doesn’t.

Converting methods that throw Exceptions to pure form

  1. Revert the Integer input parameter’s type to the non-nullable int type
  2. Change the return type of the method to a type that captures the optionality of the result when invalid inputs are provided.
good advice

Errors as values

Option 2 (above) replaces an exceptional change to control flow with a return value that captures the error state (in this case simply no result).

Better now — no Exceptions!

But what about I/O?

Loading Contents from Disk and from a URL

The loadContents method uses Option to capture when loading succeeds or fails

If the fact that loadContents failed due to an IOException was significant (or something we wanted to work with) we could capture that in a number of ways

  1. Go style using a Tuple that can contain 2 different types and values
  2. Using an Either type that holds either the correct result or an error result

Using a Tuple type

In Go methods that may return errors also return a form of Tuple, in the example below f can be present or nil, err can be present or nil.

Error handling in Go : https://blog.golang.org/error-handling-and-go

After calling loadContents we would have to check if there is an error present before handling, and if there is value present before working with it, hopefully Dysfunctional Programming In Java : No nulls allowed has convinced you this is not a good idea.

⛔️stop⛔️ don’t do this! This is not making illegal states unrepresentable. Instead we need a data type with two type parameters, one for the error and one for the result that holds only one value — either the error or the result.

Using an Either type

Either<IOException,String> contentsOrError;

Rather than check if contentsOrError has a value of the left type or the right type. Normally we use functional composition to tee up chained operations to handle both cases.

A LazyEither

We can refactor the assignment of contents from

import static cyclops.control.Eval.later;Maybe.fromEval(later(this::loadContents))   
.concatMap(Function.identity());

to something like this :-

LazyEither.later(this::loadContents);

And that gives us the same characteristics as we had with Maybe

  1. Is lazily populated on first access ✅
  2. Is only populated once due to Memoization ✅
  3. Does not throw Exceptions if the I/O data is unavailable ✅
  4. Represents the fact the contents data maybe present or absent ✅
  5. 🆕 IOException is captured if one is thrown during loading ✅

The full assignment of contents to an Either looks like this

Let’s unpack this see Use the types Luke : Creating a LazyEither from the types and knowledge

A reactive Either

asynchronously populating a CompletableEither (with Java 10 local type inference — var!)

One difference to note, is that CompletableEither is built on top of the reactive-streams API, which uses Throwable to represent an error. The mapLeft call to this::ioOrThrow casts Throwable to IOException if an error is present and is of the correct type (otherwise an Exception is thrown).

This ioOrThrow implementation looks like this:-

private IOException ioOrThrow(Throwable t){
if(t instanceof IOException){
return (IOException)t;
}
throw ExceptionSoftener.throwSoftenedException(t);
}

Try to catch Exceptions

We can refactor loadContents to use Try::withCatch which allows us to explicitly define which Exceptions we will catch (avoiding hiding unexpected errors!).

Try has two type parameters!

If you wish to collapse down to a single type parameter and drop the Exception use toOption

And to drop down to a single type parameter but keep the Exception around (if your Try fails), use to(Future::fromTry)

Try with resources

Note the three input parameters to Try::withResources

  1. The BufferedReader to be used and closed
  2. A function that recieves the BufferedReader to use
  3. The Exception type to catch

The complete code with Either and Try looks like this

The key to functional not dysfunctional Exception handling

Photo by Lucas Mordzin on Unsplash
  • Failing to handle error states
  • Failing to anticipate error states
  • Hiding error states (e.g. catch Exception)
  • Complecting control flow

When we switch to using values (via Option, Either or Try) we solve (or at least get compiler help in solving all of these

  • Failing to handle error states [The type system let’s us know an error value is possible, we have to work with the supplied Either / Option / Try] ✅
  • Failing to anticipate error states [The type system let’s us know an error value is possible] ✅
  • Hiding error states (e.g. catch Exception) [The type system can let us know the specific error that is possible — all other errors should result in fatal actual Exceptions that we find and fix as bugs — failing fast and early!] ✅
  • Complecting control flow [No GOTOs or code jumps] ✅

Getting Cyclops

Maven

<dependency>
<groupId>com.oath.cyclops</groupId>
<artifactId>cyclops</artifactId>
<version>10.0.4</version>
</dependency>

Gradle

compile group: 'com.oath.cyclops', name: 'cyclops', version: '10.0.4'

John McClean

Written by

Architecture @ Verizon Media. Maintainer of Cyclops. Twitter @johnmcclean_ie

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade