scala-loggingを使ってみる

概要

例外の取り扱いをする場合にどれを使うのが適切なのかわからなくなったので、scala.util.control.Exceptionとscala.util.Try、両クラスの各機能をざっと使ってみる。

前半はscala.util.control.Exception, 後半はscala.util.Try。

@createdAt: 2015/08/29
@versions scala 2.11

広告枠

allCatch optでOptionを戻り値に

とりあえずallCatchですべての例外をcatchする処理を書く。

最も手軽に使えそうなのがopt。例外だったらNone、成功すればSome(x)を返す。

import scala.util.control.Exception._

def divide(x: Int, y: Int) = allCatch opt { x / y }

var result = divide(0, 0)
println(result)
  #=> None

result = divide(0, 3)
println(result)
  #=> Some(0)

上記コードではallCatch optの中で除算を実行している。

1度目の呼び出し(divide(0, 0))は0/0を実行しているので例外になってNoneが返る。2度目の呼び出しはyに0以外を渡しているのでSome(計算結果)が返る。

allCatch either/withTryでEitherやSuccess/Failureを戻り値に

eitherでLeftとRightの値が返るようにしてみる。

import scala.util.control.Exception._

def divide(x: Int, y: Int) = allCatch either { x / y }

divide(0, 0) match {
  case Left(e)  => e.printStackTrace()
  case Right(i) => println(i)
}

withTryを使うとSuccess/Failureで返る。

import scala.util.control.Exception._

  def divide(x: Int, y: Int) = x / y

  allCatch withTry { divide(0, 0) } match {
    case Success(i) => println(i)
    case Failure(t) => t.printStackTrace()
  }

allCatch withApply andFinallyでtry catch finnaly

withTryよりもwithApplyの方が好きかも。withApplyの後に例外時の処理、applyの後に処理を書く。

import scala.util.control.Exception._

def divide(x: Int, y: Int) = x / y

val result = allCatch withApply { t: Throwable => -1 } apply divide(0, 0)
println(result)
  //=> -1

これにandFinallyを使うとfinallyも書ける。

import scala.util.control.Exception._

def divide(x: Int, y: Int) = x / y

val result = allCatch withApply { t: Throwable => -1 } andFinally { println("finally") } apply divide(0, 0)
  //=> finally
println(result)
  //=> -1

allCatch withApply f1 and Finally f2 apply f3 のように書けるのは何かとやりやすい。

optやeitherでもthrowされる例外

optやeitherなどを使って例外を処理した場合、ControlThrowableInterruptedExceptionはcatchされても再度throwされるらしい。breakを途中で止められても困るしね。

下記の例では途中でBreakControl(ControlThrowableを継承している)をthrowしている為、withApplyは呼び出されずにそのまま例外がthrowされる。

  // scala.util.control.BreakControlを起こすだけの関数
  def break() = Breaks.break()

  val result = allCatch withApply { t: Throwable => -1 } apply break()
    //=> Exception in thread "main" scala.util.control.BreakControl
  println(result)

nonFatalCatch

nonFatalCatchでは深刻でない例外のみcatchする。具体的にはVirtualMachineError, ThreadDeath, LinkageErrorなどが対象になるらしい。

下記の場合、1つ目のnonFatalCatchはException(fatalでない)なのでNoneが返るが、2つ目はLinkageErrorをthrowしているので例外がthrowされる。

var result = nonFatalCatch opt { throw new Exception }
println(result)
  //=> None

result = nonFatalCatch opt { throw new LinkageError() }
  //=> Exception in thread "main" java.lang.LinkageError
println(result)

ultimately

ultimatelyを使うとcatchなしでfinallyのみ記述できる。

  def divide(x: Int, y: Int) = x / y

  ultimately { println("finally") } { divide(0, 0) }

warningならログだけ出力してOptionを返したい

scala-loggingが入ってるものとして、LazyLoggingを継承したtrait作って、そこにoptWithWarnというメソッドを追加してみる。

import scala.util.control.Exception._

trait LazyLogging extends com.typesafe.scalalogging.LazyLogging {

  def optWithWarn[U](body: => U): Option[U] =
    allCatch withApply { t: Throwable => logger.warn(t.getMessage, t); None } apply { Some(body) }
}

これで例外時はwarnを出してNoneを返す処理になるはず。未テスト。

ignoringで例外を無視する

ignoringを使うと例外を無視する。但しclassofで例外を指定しないといけない。

  def something() = { println("something"); throw new Exception }

  val result = ignoring(classOf[Throwable]) { something() }
    //=> something
  println(result)
    //=> ()

handlingで例外毎にハンドリング

handlingでorして複数の例外の処理を書いてみる。

とりあえず単一の例外を取得。

def divide(x: Int, y: Int) = { x / y }

handling(classOf[ArithmeticException]) by { e: Throwable => println("ArithmeticException") } apply { divide(0, 0) }

handlingの引数には複数クラスを指定できる。handling(classOf[ArithmeticException], classOf[NumberFormatException])のように。

またorで繋ぐことで例外ごとにhandlingを変えることもできる。

(handling(classOf[ArithmeticException]) by { e: Throwable => println("ArithmeticException") }) or
  (handling(classOf[NullPointerException]) by { e: Throwable => println("NullPointerException") }) or
  (handling(classOf[NumberFormatException]) by { e: Throwable => println("NumberFormatException") }) apply
  { divide(0, 0) }

handlingの処理をあらかじめ記述しておくとスッキリする。

def divide(x: Int, y: Int) = { x / y }
val h1 = handling(classOf[NumberFormatException]) by { e: Throwable => println("NumberFormatException") }
val h2 = handling(classOf[NullPointerException]) by { e: Throwable => println("NullPointerException") }
val h3 = handling(classOf[NumberFormatException]) by { e: Throwable => println("NumberFormatException") }

h1 or h2 or h3 apply divide(0, 0)

scala.util.Try

Tryでwrapすれば、getOrElseで失敗時の値を設定したり、recoverで例外のハンドリングができる。

下記はgetOrElseで失敗時は-1を返すようにしている。

import scala.util.Try

def divide(x: Int, y: Int) = Try { x / y }

val result = divide(0, 0).getOrElse(-1)
println(result)
  #=> -1

getを用いると、例外でなければ値が取得でき、例外が発生していれば当該の例外がthrowされる。

def divide(x: Int, y: Int) = Try { x / y }

val result1 = divide(4, 2).get
println(result1)
  //=> 2

val result2 = divide(0, 0).get
println(result2)
  //=> Exception in thread "main" java.lang.ArithmeticException: / by zero

Exceptionの方でもやったようにSuccess/Failureでパターンマッチできる。

def divide(x: Int, y: Int) = Try { x / y }

divide(0, 0) match {
  case Success(i) => println(i)
  case Failure(e) => println(e.getMessage)
}
  //=> / by zero

foreachで成功時だけ処理を行わせる。

下記のケースでは前者は例外にならないので結果が標準出力され、後者は例外が発生しているので何も出力されない。

def divide(x: Int, y: Int) = Try { x / y }

divide(3, 1) foreach println
  //=> 3
divide(0, 0) foreach println
  //=>

recoverで例外のハンドリング

recoverはFailureの中身に対して処理をしてSuccessに置き換えることができる。

下記の例では0除算で例外が起きているが、recover内でArithmeticExceptionの場合は標準出力して-1を返すと記述されている為、結果はSuccess(-1)が返る。

def divide(x: Int, y: Int) = Try { x / y }

val t = divide(3, 0) recover {
  case e: IOException           => { println("IOException"); -1 }
  case e: ArithmeticException   => { println("ArithmeticException"); -1 }
  case e: NumberFormatException => { println("NumberFormatException"); -1 }
  case e                        => throw e
}
  //=> ArithmeticException

println(t)
  //=> Success(-1)

recoverWithだと勝手にSuccessでwrapされない。

下記の例では指定した例外の場合はSuccessで返し、それ以外の例外であればFailureで返している。

val t = divide(3, 0) recoverWith {
  case e: ArithmeticException   => Success(-1)
  case e: NumberFormatException => Success(-1)
  case e                        => Failure(e)
}
  //=> ArithmeticException

println(t)
  //=> Success(-1)

Tryは基本、NonFatal

TryはNonFatalな例外のみFailureで返し、それ以外だとそのまま例外をthrowする。

下記のようにFatalなLinkageErrorが発生した場合や、BreakなどのControlThrowable、InterruptedExceptionが発生した場合はcatchされない。

def something() = Try { throw new LinkageError() }

something()
  //=> Exception in thread "main" java.lang.LinkageError