Use Try Instead of try-catch Block

Java and Scala have try-catch-finally or try-catch, for short, block to manage exceptions and errors. But, unlike Java, Scala has no checked exceptions and treats all Java exceptions as unchecked exceptions a.k.a runtime exceptions.

The try-catch syntax and usage are about the same for both languages mentioned. Nothing difficult here. However, for most of the part, it is preferably or idiomatic to use Try or Try[+T] in Scala than the try-catch block. Try has more benefits compare to the try-catch block because it is easily chained together and malleable.

Take for example,

Try{foo(10)}.flatMap(_ + 1).map(_.toString)

The integer returns by foo(...) is increased by 1 and then converts to a string. Yet, the value in Try can be further be manipulated on and on as it passes from function to function. Value returns by function inside Try is contained inside Success. Use get to extract the value.

Even if there is an exception thrown by foo(...), the whole chain is still intact. The final result at this point is an instance of Failure holding the exception thrown by foo(...).

Try is the super class of Success and Failure.

The better part of it is the whole chain can be assigned to a value,

val t: Try[String] = Try{foo(10)}.flatMap(_ + 1).map(_.toString)

and pass to the next function or return to the caller.

Let’s look at a longer example of how try-catch is managed in Java compares to Scala. The requirements and the Java implemention are copied from my previous article with the class name changed,

Example 1: foo(...) should throw an exception if r is more 60 and bar(...) should throw an exception if r is more than 70. If r is less than 60, the sum of bar(...) and foo(...) and 20 shall be printed. In the event that foo(…) or bar(…) throws an exception, 20 shall be printed instead.

public class TypicalJavaExceptionHandler {
public static void main(String[] args) {
int r = Integer.parseInt(args[0]);
TypicalJavaHandling handling = new TypicalJavaHandling();
System.out.println(handling.fuzz(r));
}

public int fuzz(int r) {

// version 1
try {
return handling.bar(r) + handling.foo(r) + 20;
} catch (Exception e) {
return 20;
}
}

public int foo(int r) {
if (r > 60) throw new IllegalArgumentException("foo value more than 60");
return 100 / r;
}

public int bar(int r) {
if (r > 70) throw new IllegalArgumentException("bar value more than 70");
return 200 / r;
}
}

And the Scala version with Try[T] instead of the try-catch block,

import scala.util.Try

object ScalaExceptionHandler {

def main(args: Array[String]) {
val r = args(0).toInt
println(fuzz(r))
}

def fuzz(r: Int) =
// version 1
Try{bar(r)}
.flatMap(x => Try{foo(r)}
.map(y => x + y + 20))
.recover{ case t => 20}

def foo(r: Int) = {
if (r > 60) throw new Exception("foo value more than 70")
100 / r
}

def bar(r: Int) = {
if (r > 70) throw new Exception("bar value more than 70")
200 / r
}
}

In this example, there is no obvious advantage over the try-catch block other than the visual aesthetic. Unfortunately, there is a change to the original requirement,

Example 2: foo(...) should throw an exception if r is more 60 and bar(...) should throw an exception if r is more than 70. If r is less than 60, the sum of bar(...) and foo(...) and 20 shall be printed. In the event of an exception is thrown from foo(…), 20 is assumed and for bar(…), 10 is assumed.

With this new requirement, Java method fuzz(…) is rewritten as,

public int fuzz(int r) {

// version 1
/*
try {
return handling.bar(r) + handling.foo(r) + 20;
} catch (Exception e) {
return 20;
}
*/

// version 2
int bar = 0;
int foo = 0;

try {
bar = bar(r);
} catch (Exception e) {
bar = 10;
}

try {
foo = foo(r);
} catch (Exception e) {
foo = 20;
}

return foo + bar;
}

and similarly in Scala,

// version 2
def fuzz(r: Int) =
// version 1
// Try{bar(r)}
// ...

// version 2
Try{bar(r)}
.recover{case e => 10}
.flatMap(x => Try{foo(r)}
.recover{case e => 20}
.map(y => x + y + 20))

Again, using Try is still clear and crispy. When an exception is thrown, recover with 10 and pass on. Again, when there is an exception thrown in the second Try, recover with 20 and finally sum all these values as if there is no exception has occurred. Obviously, when there is no exception, the values x and y are what return by the functions bar(…) and foo(…).

The previous Try chain can be rewritten as,

for {
x 10}
y 20}
} yield x + y + 20

This syntactical sugar code block will be converted to the previous flatMap and map equivalents.

Summary

Try allows the developers to compose the value transformations and exception recoveries. They are also assignable to a value unlike the plain try-catch block which is rigid. However, do not take this as an insinuation that the try-catch block is evil and avoid using them. They are the building blocks for greater things like Try and serve different purposes and it is especially true for library and framework developers. As developers at a higher level, Try should be the first choice.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s