I use a Java lib that relies on exceptions. Simplified code below:
try {
val eventTime = eventTimeString.as[Date]
} catch {
case e: Exception =>
logger.error(s"Can't parse eventTime from $eventTimeString", e)
// take action for the bad Date string.
}
In Java I would catch only the exception from parsing a string into a Date, letting the rest go uncaught since they could be fatal. Here my understanding is that catching Exception
means to catch ANY non-fatal/non-serious exceptions. Since it is not the same a catching Throwable
it is safe—but is it truly? The rationale for using this is that unknown exceptions might be thrown from deeper in the stack and if they are not fatal, why not catch all of them. This has always been a problem in Java where it is easy to find possible exceptions from the direct call you are making but not from deeper. Is this the Scala solution that basically means, 'catch all recoverable exceptions"?
My question is; is the above code considered good Scala style, is it "safe", meaning better than catching only the string to Date cast exception.
Addressing the style aspect of the question, Try
, as suggested by @LuisMiguelMejiaSuarez, provides a more idiomatic Scala style like so
Try(eventTimeString.as[Date]) match {
case Success(eventTimeDate) => // work with eventTimeDate
case Failure(e: IllegalArgumentException) => // work with e
case Failure(e: NullPointerException) => // work with e
...
case Failure(e) => // work with e
}
Syntactically it appears not much different, however conceptually it is quite a shift because Success
and Failure
represent regular values as opposed to some exceptional control-structure. Success
is a value like 7
is value, whilst try-catch
is more like while
or if-else
control facility.
Wrapping any library call that might throw in Try
, provided by, say, Java libraries, we could make use of for-yield sugar to chain calls like so
for {
a <- Try(foo)
b <- Try(bar)
c <- Try(qux)
} yield {
// work with a, b and c
}
where
def foo: Int = {
throw new TimeoutException("foo")
42
}
def bar: String = {
throw new IllegalArgumentException("bar")
"hello"
}
def qux: Boolean = {
throw new NullPointerException("qux")
true
}
We can read this chain sequentially without having to interrupt our flow of thought and try to understand how some exceptional control structure fits into the algorithm.
Regarding safety aspect of the question, arguably, we should not catch fatal exception such as LinkageError
, and indeed Try
does not match the following exceptions
VirtualMachineError
ThreadDeath
InterruptedException
LinkageError
ControlThrowable
as shown by the way it is constructed
object Try {
def apply[T](r: => T): Try[T] =
try Success(r) catch {
case NonFatal(e) => Failure(e)
}
}
where NonFatal
is
object NonFatal {
def apply(t: Throwable): Boolean = t match {
case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable => false
case _ => true
}
def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With