Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to catch an Exception object

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.

like image 797
pferrel Avatar asked May 30 '19 19:05

pferrel


Video Answer


1 Answers

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
}
like image 53
Mario Galic Avatar answered Oct 18 '22 22:10

Mario Galic