Functional Java by Example | Part 3 – Don’t Use Exceptions to Control Flow
This is part 3 of the series called “Functional Java by Example”.
The example I’m evolving in each part of the series is some kind of “feed handler” which processes documents. In previous parts I started with some original code and applied some refactorings to describe “what” instead of “how”.
In order to help the code going forward, we need to get rid of the good ol’ java.lang.Exception
. (disclaimer: we can’t actually get rid of it) That’s where this part comes in.
If you came here for the first time, it’s best to start reading from the beginning. It helps to understand where we started and how we moved forward throughout the series.
These are all the parts:
- Part 1 – From Imperative to Declarative
- Part 2 – Tell a Story
- Part 3 – Don’t Use Exceptions to Control Flow
- Part 4 – Prefer Immutability
- Part 5 – Move I/O to the Outside
- Part 6 – Functions as Parameters
- Part 7 – Treat Failures as Data Too
- Part 8 – More Pure Functions
I will update the links as each article is published. If you are reading this article through content syndication please check the original articles on my blog.
Each time also the code is pushed to this GitHub project.
Getting up to speed about Exceptions
Our java.lang.Exception
has been around since Java 1.0 – and has basically been our friend in good times and nemesis at other times.
There’s not much to talk about them, but if you want to read up on a few sources, here are my favorites:
- Exceptions in Java (JavaWorld)
- Exceptions in Java – GeeksforGeeks (geeksforgeeks.org)
- 9 Best Practices to Handle Exceptions in Java (stackify.com)
- Best Practices for Exception Handling (onjava.com)
- Java Exception Interview Questions and Answers (journaldev.com)
- Exception handling in java with examples (beginnersbook.com)
- Java Exception Handling (Try-catch) (hackerrank.com)
- Top 20 Java Exception Handling Best Practices – HowToDoInJava (howtodoinjava.com)
- Exception Handling & Assertion in Java – NTU (ntu.edu.sg)
- Exception Handling: A Best Practice Guide (dzone.com)
- 9 Best Practices to Handle Exceptions in Java (dzone.com)
- Fixing 7 Common Java Exception Handling Mistakes (dzone.com)
- Java Practices -> Checked versus unchecked exceptions (javapractices.com)
- Common mistakes with exceptions in Java | Mikael Ståldal’s technical blog (staldal.nu)
- 11 Mistakes Java Developers Make When Using Exceptions (medium.com/@rafacdelnero)
- Are checked exceptions good or bad? (JavaWorld)
- Checked exceptions: Java’s biggest mistake | Literate Java (literatejava.com)
- Unchecked Exceptions—The Controversy (docs.oracle.com)
- The Trouble with Checked Exceptions (artima.com)
- Exceptions in Java: You’re (Probably) Doing It Wrong (dzone.com)
- Java theory and practice: The exceptions debate – IBM (ibm.com)
- Java’s checked exceptions were a mistake (and here’s what I would like to do about it (radio-weblogs.com)
- Buggy Java Code: Top 10 Most Common Mistakes That Java Developers Make | Toptal (toptal.com)
You on Java 8 already? Life became so much better! I… Err…oh, wait.
- Error handling with Java input streams – Javamex (javamex.com)
- Handling checked exceptions in Java streams (oreilly.com)
- Exceptional Exception Handling In JDK 8 Streams (azul.com)
- Java 8 Functional Interfaces with Exceptions (slieb.org)
- Repackaging Exceptions In Streams – blog@CodeFX (blog.codefx.org)
- How to handle Exception in Java 8 Stream? – Stack Overflow (stackoverflow.com)
- Checked Exceptions and Streams | Benji’s Blog (benjiweber.co.uk)
- A story of Checked Exceptions and Java 8 Lambda Expressions (javadevguy.wordpress.com) – nice war story!
- hgwood/java8-streams-and-exceptions (github.com)
- …
Ok, seems that there’s no way you can actually do it right.
At least, after reading above list, we’re now completely up-to-speed on the topic ��
Luckily I don’t have to write a blog post any more about what’s been covered for 95% already in above articles, but I’ll focus here on the one Exception
we actually have in the code ��
Side effects
Since you’re reading this post, you’re probably interested in why this all has to do with functional programming.
On the road to approaching your code in a more “functional way”, you may have encountered the term “side effect” and that it’s a “bad thing”.
In the real world, a side effect is something you did not intend to happen, and you might say it’s equivalent to an “exceptional” situation (you would indicate with an exception), but it has a more strict meaning in a Functional Programming context.
The Wikipedia-article about a Side effect says:
Side effect (computer science) In computer science, a function or expression is said to have a side effect if it modifies some state outside its scope or has an observable interaction with its calling functions or the outside world besides returning a value. … In functional programming, side effects are rarely used.
So let’s see how our FeedHandler code currently looks like after the first two articles in this series:
class FeedHandler { Webservice webservice DocumentDb documentDb void handle(List<Doc> changes) { changes .findAll { doc -> isImportant(doc) } .each { doc -> try { def resource = createResource(doc) updateToProcessed(doc, resource) } catch (e) { updateToFailed(doc, e) } } } private Resource createResource(doc) { webservice.create(doc) } private boolean isImportant(doc) { doc.type == 'important' } private void updateToProcessed(doc, resource) { doc.apiId = resource.id doc.status = 'processed' documentDb.update(doc) } private void updateToFailed(doc, e) { doc.status = 'failed' doc.error = e.message documentDb.update(doc) } }
There’s one place where we try-catch exceptions, and that’s where we loop through the important documents and try to create a “resource” (whatever that is) for it.
try { def resource = createResource(doc) updateToProcessed(doc, resource) } catch (e) { updateToFailed(doc, e) }
In code above catch (e)
is Groovy shorthand for catch (Exception e)
.
Yes, that’s the generic java.lang.Exception
which we’re catching. Could be any exception, including NPE.
If there’s no exception thrown from the createResource
method, we update the document (“doc”) to ‘processed’, else we update it to ‘failed’. BTW, even updateToProcessed
can throw an exception too, but for the current discussion I’m actually only interested in a successful resource creation.
So, above code works (I’ve got the unit tests to prove it :-)) but I’m not happy with the try-catch
statement as it is now. I’m only interested in successful resource creation, and, silly me, I could only come up with createResource
either returning a successful resource or throwing an exception.
Throwing an exception to signal something went wrong, get the hell out of dodge, have caller catch the exception in order to handle it, is why exceptions were invented right? And it’s better than returning null
right?
It happens all the time. Take some of our favorite frameworks, such as EntityManager#find
from the JPA spec:
Arg! Returns null
.
Returns:
the found entity instance or null if the entity does not exist
Wrong example.
Functional Programming encourages side-effect free methods (or: functions), to make the code more understandable and easier to reason about. If a method just accepts certain input and returns the same output every time – which makes it a pure function – all kinds of optimizations can happen under the hood e.g. by the compiler, or caching, parallelisation etc.
We can replace pure functions again by their (calculated) value, which is called referential transparancy.
In previous article, we’ll already extracted some logic into methods of their own, such as isImportant
below. Given the same document (with the same type
property) as input, we’ll get the same (boolean) output every time.
boolean isImportant(doc) { doc.type == 'important' }
Here there’s no observable side effect, no global variables are mutated, no log file is updated – it’s just stuff in, stuff out.
Thus, I would say that functions which interact with the outside world through our traditional exceptions are rarely used in functional programming.
I want to do better than that. Be better.
Optional to the rescue
As Benji Weber expresses it:
There are different viewpoints on how to use exceptions effectively in Java. Some people like checked exceptions, some argue they are a failed experiment and prefer exclusive use of unchecked exceptions. Others eschew exceptions entirely in favour of passing and returning types like Optional or Maybe.
Ok, let’s try Java 8’s Optional
so signal whether a resource can or can not be created.
Let’s change the our webservice interface and createResource
method to wrap and return our resource in an Optional
:
//private Resource createResource(doc) { private Optional<Resource> createResource(doc) { webservice.create(doc) }
Let’s change the original try-catch
:
try { def resource = createResource(doc) updateToProcessed(doc, resource) } catch (e) { updateToFailed(doc, e) }
to map
(processing resource) and orElseGet
(processing empty optional):
createResource(doc) .map { resource -> updateToProcessed(doc, resource) } .orElseGet { /* e -> */ updateToFailed(doc, e) }
Great createResource
method: either correct result comes back, or an empty result.
Wait a minute! The exception e
we need to pass into updateToFailed
is gone: we have an empty Optional
instead. We can’t store the reason why it failed — which we do need.
May be an Optional
just signals “absence” and is a wrong tool for our purpose here.
Exceptional completion
Without the try-catch
and with the map-orElseGet
instead, I do like the way the code started to reflect the “flow” of operations more. Unfortunately, using Optional
was more appropriate for “getting something” or “getting nothing” (which names like map
and orElseGet
also suggested) and didn’t give us the opportunity to record a reason for failing.
What’s another way to either get the successful result or get the reason for failing, still approaching our nice way of reading?
A Future
. Better yet: a CompletableFuture
.
A CompletableFuture
(CF) knows how to return a value , in this way it’s similar to an Optional
. Usually a CF is used for getting a value set in the future, but that’s not what we want to use it for…
From the Javadoc:
A Future that …, supporting … actions that trigger upon its completion.
Jip, it can signal “exceptional” completion — giving me the opportunity to act upon it.
Let’s change the map
and orElseGet
:
createResource(doc) .map { resource -> updateToProcessed(doc, resource) } .orElseGet { /* e -> */ updateToFailed(doc, e) }
to thenAccept
(processing success) and exceptionally
(processing failure):
createResource(doc) .thenAccept { resource -> updateToProcessed(doc, resource) } .exceptionally { e -> updateToFailed(doc, e) }
The CompletableFuture#exceptionally
method accepts a function with our exception e
with the actual reason for failure.
You might think: tomayto, tomahto. First we had try-catch
and now we have thenAccept-exceptionally
, so what’s the big difference?
Well, we can obviously not get rid of the exceptional situations, but we’re now thinking like a resident of Functionalville would: our methods start to become functions, telling us something goes in and something goes out.
Consider it a small refactoring we need towards part 4, limiting the amount of side effects in our code even more, and part 5.
This is it for now
For reference, here’s the full version of the refactored code.
class FeedHandler { Webservice webservice DocumentDb documentDb void handle(List<Doc> changes) { changes .findAll { doc -> isImportant(doc) } .each { doc -> createResource(doc) .thenAccept { resource -> updateToProcessed(doc, resource) } .exceptionally { e -> updateToFailed(doc, e) } } } private CompletableFuture<Resource> createResource(doc) { webservice.create(doc) } private boolean isImportant(doc) { doc.type == 'important' } private void updateToProcessed(doc, resource) { doc.apiId = resource.id doc.status = 'processed' documentDb.update(doc) } private void updateToFailed(doc, e) { doc.status = 'failed' doc.error = e.message documentDb.update(doc) } }
—
Published on Java Code Geeks with permission by Ted Vinke, partner at our JCG program. See the original article here: Functional Java by Example | Part 3 – Don’t Use Exceptions to Control Flow Opinions expressed by Java Code Geeks contributors are their own. |