0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ZIOのエラー処理

Last updated at Posted at 2023-09-28

ZIOの3種類のエラー

ZIOのエラーは全体で以下の3種類に大別されます。

Failures

ZIOにおける最も代表的なエラーであると言えるかもしれません。このエラーは一般的に予測されるエラーを表しています。
予測可能なエラーとなるため、このエラーについては適切なハンドリング処理を行うことが期待されています。
そのため、ZIO[R,E,A]型の型パラメータの第二引数に現れるという特徴があります。ZIO[R,E,A]という場合はEになります。
具体的には以下の様な場合に発生するエラーです。

  1. ZIO.attempt内の処理でのエラー

Defects

このエラーは一般的に予測できないエラーとなります。
そのため、このエラーの型についても同様に予測できず、ZIO型の型パラメータ上にも現れません。
具体的には以下の様な場合に発生するエラーです。

  1. ZIO.succeed内の処理でのエラー
  2. ZIO型を返す関数の引数における処理でのエラー

Fatal

このエラーは予測できないStackOverflowErrorといったJVM関係のエラーとなります。
このエラーが発生した場合、ZIOアプリケーションは強制終了するため、ハンドリング処理は行えず、出来る処理としてはエラーに関するログ出力処理のみになります。

  1. ZIO型を返さず、末尾再帰による最適化などがなされていない通常の再起処理におけるStackOverflowError

それぞれの具体例

これからこれら3つのエラーについて、実際にエラーを意図的に発生させ、具体的に見ていきます。

Failures

このfailureエラーについては意図的に発生させる方法はいくつかあるのですが、今回はZIO.fail関数を使った方法により発生させ、どのような挙動を示すかについて確認します。
Hello, World!プログラムについて以下の様に改変します。

ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] =
    for{
      _ <- ZIO.fail(new Exception("This is a failure test."))
      _ <- Console.printLine(
      s"${new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss").format (currentDate)} Hello, World!"
      )
    } yield ()
}
main.scala
object MainApp extends ZIOAppDefault {
  def run: ZIO[Any, Throwable, Unit] = ApplicationService.consoleOutput().provide(
    ZLayer.fromZIO(ZIO.attempt {
      import java.text.SimpleDateFormat
      // 任意の日付文字列
      val inpDateStr = "2023/07/25 17:46:00"

      // 取り扱う日付の形にフォーマット設定
      val sdformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")

      // Date型に変換( DateFromatクラスのparse() )
      sdformat.parse(inpDateStr)
    }),
    ApplicationServiceImpl.layer
  ).catchAll(failure => Console.printError("Failure:" + failure.toString))
}

こちらについて実行すると以下の様に出力されます。

Failure:java.lang.Exception: This is a failure test.

2023/07/25 Hello, World!が出力されなかったのはこれを出力する処理の前にfailureエラーが発生したためとなります。
発生したfailureエラーは上層に伝播していき、catchAll関数で最終的に処理されました。
その結果、このような出力が行われました。
catchAll関数はfailureエラーを処理する関数になります。
このようにfailureエラーの型についてはZIO[R,E,A]型の型パラメータの第二引数と一致する必要があります。
今回の場合ですと第二引数がThrowableであるため、そのサブクラスであるException型とすることができます。

Defects

次にdefectエラーを見ます。
今回はZIO.die関数を使った方法により発生させます。
先ほどのプログラムを以下のように改変します。

ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, IOException, Unit] =
    for{
      _ <- ZIO.die(new ArithmeticException("divide by zero"))
      _ <- Console.printLine(
      s"${new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss").format (currentDate)} Hello, World!"
      )
    } yield ()
}
ApplicationService.scala
package application.service

import zio.ZIO

import java.io.IOException

trait ApplicationService {
  def consoleOutput(): ZIO[Any, IOException, Unit]
}

object ApplicationService {
  def consoleOutput(): ZIO[ApplicationService, IOException, Unit] =
    ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}

こちらについて実行すると以下の様に出力されます。

timestamp=2023-07-28T06:50:49.586017200Z level=ERROR thread=#zio-fiber-1 message="" cause="Exception in thread "zio-fiber-4" java.lang.ArithmeticException: divide by zero
        at application.service.impl.ApplicationServiceImpl.consoleOutput$$anonfun$1(ApplicationServiceImpl.scala:13)
        at zio.ZIO$.die$$anonfun$1(ZIO.scala:3081)
        at zio.ZIO$.failCause$$anonfun$1$$anonfun$1$$anonfun$1(ZIO.scala:3155)
        at application.service.impl.ApplicationServiceImpl.consoleOutput(ApplicationServiceImpl.scala:13)
        at application.service.impl.ApplicationServiceImpl.consoleOutput(ApplicationServiceImpl.scala:17)
        at <empty>.MainApp.run(main.scala:6)
        at <empty>.MainApp.run(main.scala:19)"

Failure:という文字列は表示されず、catchAll関数でこのエラーが処理されなかったことがわかります。
このことからこのdefectエラーは先ほどのfailureエラーとは別の種類のエラーであることがわかります。
また、今回ZIO型の型パラメータの第二引数をThrowableからIOExceptionに変更しておりますが、ZIO.dieで発生させているエラーはArithmeticExceptionとなっています。ところがコンパイルエラーとはならず実行自体はできています。
これは先述の通りdefectエラーは一般的に予測できない類のエラーを表しているため、型パラメータの第二引数とは直接的に関係しないためとなります。
試しに_ <- ZIO.die(new ArithmeticException("divide by zero"))の部分を_ <- ZIO.fail(new ArithmeticException("divide by zero"))と変更するとコンパイルエラーとなることがわかります。


catchAll関数はfailureエラーを処理するための関数でしたが、defectエラーを処理するための関数としてcatchAllDefect関数があります。
以下のようにプログラムを変更してください。

main.scala
  ).catchAll(failure => Console.printError("Failure:" + failure.toString)).catchAllDefect(defect =>
    Console.printError("Defect:" + defect.toString)
  )

これを実行すると以下の様に出力されます。

Defect:java.lang.ArithmeticException: divide by zero

catchAllDefect関数によりdefectエラーを処理することが出来ました。

Fatal

最後にFatalエラーです。
先ほどのプログラムについて以下の様に変更します。

ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] =
    for {
      _ <- ZIO
        .attempt(
          throw new StackOverflowError(
            "The call stack pointer exceeds the stack bound."
          )
        )
      _ <- Console.printLine(
        s"${new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(currentDate)} Hello, World!"
      )
    } yield ()
}
ApplicationService.scala
trait ApplicationService {
  def consoleOutput(): ZIO[Any, Throwable, Unit]
}

object ApplicationService {
  def consoleOutput(): ZIO[ApplicationService, Throwable, Unit] =
    ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}

StackOverflowErrorを意図的に発生させるように修正しました。また、型パラメータの第二引数の型についてもThrowable型に戻しています。
これを実行すると以下の様に出力されます。

**** WARNING ****
Catastrophic error encountered. Application not safely interrupted. Resources may be leaked. Check the logs for more details and consider overriding `Runtime.reportFatal` to capture context.
java.lang.StackOverflowError: The call stack pointer exceeds the stack bound.
(以下省略)

出力された文字列にFailure:Defect:も含まれないことからfailureエラーを処理するcatchAll関数でもdefectエラーを処理するcatchAllDefect関数でも処理できていないことがわかり、これらとは別種のエラーであることが確かめられました。

終わりに

今回はZIOのエラー処理について見てきました。
前章のZIOのDIについてと併せ、ZIO[R, E, A] 型の意味合いについての基本的な理解が得られたかと思います。

次章ではZIO Quillを例としてZIOエコシステムのライブラリの利用、DB接続について見ていきます。

前章:ZIOのDIについて
次章:DB接続(ZIO Quill)

演習

  1. 今回の3つのエラーについて実際に動かして挙動を確かめてください。
    1. Failuresの解答例は以下になります。
      https://github.com/hatuda/zio-practice/tree/CHAPTER3-Failures
    2. Defectsの解答例は以下になります。
      https://github.com/hatuda/zio-practice/tree/CHAPTER3-Defects
    3. Fatalの解答例は以下になります。
      https://github.com/hatuda/zio-practice/tree/CHAPTER3-Fatal
  2. 今回の方法とは別の方法でそれぞれのエラーを発生させ、挙動を確かめてください。
    1. Failuresの解答例は以下になります。
      https://github.com/hatuda/zio-practice/tree/CHAPTER3-Failures-Another
    2. Defectsの解答例は以下になります。
      https://github.com/hatuda/zio-practice/tree/CHAPTER3-Defects-Another
    3. Fatalの解答例は以下になります。
      https://github.com/hatuda/zio-practice/tree/CHAPTER3-Fatal-Another
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?