32

Handling Exceptions: Functional Style

 5 years ago
source link: https://www.tuicool.com/articles/hit/uEJJBvz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java supports checked exceptions from the very start. With Java 8, the language element lambda and the RT library modifications supporting stream operations introduced the functional programming style to the language. Functional style and exceptions are not always the best of friends. In this article, I will describe a simple library that handles exceptions somehow similar to how null is handled using Optional .

The library works (after all it is a single Class and some inner classes, but really not many). On the other hand, I am not absolutely sure that using the library will not deteriorate the programming style of the average programmer. It may happen that someone having a hammer sees everything as a nail. A hammer is not a good pedicure tool. Have a look at this library more like an idea and not as a final tool that tells you how to create perfect code handling exceptions.

Also, come and listen to the presentation of Michael Feathers about exceptions May 6, 2019, Zürich https://www.jug.ch/html/events/2019/exceptions.html

Handling Checked Exception

Checked exceptions have to be declared or caught like a cold. This is a major difference from null . Evaluating an expression can silently be null , but it cannot silently throw a checked exception. When the result is null , then we may use that to signal that there is no value or we can check that and use a “default” value instead of null . The code pattern doing that is:

var x = expression;
if( expression == null ){
  x = default expression that is really never null
}

The pattern topology is the same in case the evaluation of the expression can throw a checked exception, although the Java syntax is a bit different:

Type x; // you cannot use 'var' here
try{
  x = expression
}catch(Exception weHardlyEverUseThisValue){
  x = default expression that does not throw exception
}

The structure can be more complex if the second expression can also be null or may throw an exception, and we need a third expression or even more expressions to evaluate in case the former ones failed. This is especially naughty in case of an exception throwing expression because of all the bracketing:

Type x; // you cannot use 'var' here
try{
  try {
    x = expression1
  }catch(Exception e){
  try {
    x = expression2
  }catch(Exception e){
  try {
    x = expression3
  }catch(Exception e){
    x = expression4
  }}}}catch(Exception e){
  x = default expression that does not throw exception
}

In the case of null handling, we have Optional . It is not perfect to fix the million dollar problem, which is the name of designing a language having null and also an underestimation, but it makes life a bit better if used well. (And much worse if used in the wrong way, which you are free to say that what I describe in this article is exactly that.)

In the case of null resulting expressions, you can write:

var x = Optional.ofNullable(expresssion)
         .orElse(default expression that does not throw exception);

You can also write:

var x = Optional.ofNullable(expresssion1)
.or( () -> Optional.ofNullable(expression2))
.or( () -> Optional.ofNullable(expression3))
.or( () -> Optional.ofNullable(expression4))
...
.orElse(default expression that does not throw exception);

That is when you have many alternatives for the value. But you cannot do the same thing in case the expression throws an exception. Or, can you?

Exceptional

The library Exceptional ( https://github.com/verhas/exceptional )...

<groupId>com.javax0</groupId>
<artifactId>exceptional</artifactId>
<version>1.0.0</version>

implements all the methods that are implemented in Optional , one method more and some of the methods a bit differently aiming to be used the same way in case of exceptions as was depicted above for Optional in case of null values.

You can create an Exceptional value using Exceptional.of() or Exceptional.ofNullable() . The important difference is that the argument is not the value but rather a supplier that provides the value. This supplier is not the JDK Supplier because that one cannot throw an exception and that way the whole library would be useless. This supplier has to be Exceptional.ThrowingSupplier , which is exactly the same as the JDK Supplier but the method get() may throw an Exception . (Also note that only an Exception and not Throwable which you should only catch as often as you catch a red-hot iron ball using bare hands.)

What you can write, in this case, is:

var x = Exceptional.of(() -> expression) // you CAN use 'var' here
    .orElse(default expression that does not throw exception);

It is shorter and shorter is usually more readable. (Or not? That is why APL is so popular? Or is it? What is APL you ask?)

If you have multiple alternatives, you can write:

var x = Exceptional.of(() -> expression1) // you CAN use 'var' here
    .or(() -> expression2)
    .or(() -> expression3) // these are also ThrowingSupplier expressions
    .or(() -> expression4)
...
    .orElse(default expression that does not throw exception);

In case some of the suppliers may result null not only throwing an exception, there are ofNullable() and orNullable() variants of the methods. (The orNullable() does not exist in Optional but here it makes sense if the whole library does at all.)

If you are familiar with Optional and use the more advanced methods like ifPresent() , ifPresentOrElse() , orElseThrow() , stream() , map() , flatMap() , filter() , then it will not be difficult to use Exceptional . Similar methods with the same name exist in the class. The difference, again, is that in case the argument for the method in Optional is a Function ; then it is ThrowingFunction in case of Exceptional . Using that possibility, you can write code like:

private int getEvenAfterOdd(int i) throws Exception {
    if( i % 2 == 0 ){
        throw new Exception();
    }
    return 1;
}

@Test
@DisplayName("some odd example")
void testToString() {
    Assertions.assertEquals("1",
            Exceptional.of(() -> getEvenAfterOdd(1))
                    .map(i -> getEvenAfterOdd(i+1))
                    .or( () -> getEvenAfterOdd(1))
            .map(i -> i.toString()).orElse("something")
    );
}

It is also possible to handle the exceptions in functional expressions, like in the following example:

private int getEvenAfterOdd(int i) throws Exception {
    if (i % 2 == 0) {
        throw new Exception();
    }
    return 1;
}

@Test
void avoidExceptionsForSuppliers() {
    Assertions.assertEquals(14,
            (int) Optional.of(13).map(i ->
                    Exceptional.of(() -> inc(i))
                            .orElse(0)).orElse(15));
}

Last, but not least, you can mimic the ?. operator of Groovy writing

a.b.c.d.e.f

expressions, where all the variables/fields may be null and accessing the next field through them causes NPE. You can, however, write:

var x = Exceptional.ofNullable( () -> a.b.c.d.e.f).orElse(null);

Summary

Remember what I told you about the hammer: Use with care and for the greater good and other BS.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK