• Scala Error Handling의 기본 컨셉은 선언적 처리(Declarative Error Handling)으로 기존 try-catch의 명시적인 방식(Imperative Error Handling)과는 방식이 다르다.
  • 선언적 에러 처리의 장점은 아래와 같다.
    • 기존 try-catch 방식은 경우에 따라 error의 발생 원인과 try-catch로 인한 로직의 변경 추적이 어렵다. 반면 선언적 처리는 참조 무결성(referentially transparent)이 보장된다.
    • Type-safety : 함수의 정의시 return type 뿐만 아니라 어떤 type의 error로 실패하는지 알 수 있게 되어 compile time에 type-safety에 대한 점검이 가능하다.
    • Exhaustivity Checking(에러 체크의 완전성) : 컴파일 타임에 반드시 처리해야 하는 error handling 여부에 대한 검사가 가능하다.
    • Error Model : ZIO는 Exit, Cause와 같은 에러의 정보를 담고있는 모델을 자체적으로 제공한다. 에러의 유실을 막을 수 있다.
 try {
    try throw new Error("e1")
    finally throw new Error("e2")
 } catch {
   case e: Error => println(e)
 }

// Output:
// e2

---

ZIO.fail("e1")
  .ensuring(ZIO.succeed(throw new Exception("e2")))
  .catchAll {
    case "e1" => Console.printLine("e1")
    case "e2" => Console.printLine("e2")
  }

// Output:
// e1

 

  • ZIO 전용의 Error를 저장 관리하기 위한 객체인 Cause에 대한 이해가 필수적이다.
    • https://zio.dev/reference/core/cause
    • It allows us to take a base type E that represents the error type and then capture the sequential and parallel composition of errors in a fully lossless fashion.
    • (번역) Cause는 에러(Exception)의 class type을 나타내는 E를 획득할 수 있게 하고, 에러의 누락 방직 차원에서 순차적인 혹은 병렬적인 에러의 포착을 제공한다.
    • Cause의 내부 구조는 다음과 같다
      • Empty : Cause의 초기상태로 ZIO.succeed(5)와 같이 error가 없는 상태를 의미한다.
      • Fail : expected error의 type E를 의미한다.
      • Die : defect(unexpected error)의 type E를 의미한다.
      • Iterrupt : fiber 등의 멀티 쓰레드 환경에서 interruption을 의미한다.
      • Stackless : stack trace 와 excution trace 정보를 담고 있다. (stack trace의 노출 레벨이 Die와는 다르다)
      • Both : 병렬 프로그램밍 상에서 2개 이상의 error가 발생하였을때의 정보를 담고 있다.
      • Then : 순차적 에러가 발생했을때 Error 객체 저장(고전 try-catch 모델 혹은 fail.ensuring 등)
sealed abstract class Cause[+E] extends Product with Serializable { self =>
  import Cause._
  def trace: Trace = ???

  final def ++[E1 >: E](that: Cause[E1]): Cause[E1] = Then(self, that)
  final def &&[E1 >: E](that: Cause[E1]): Cause[E1] = Both(self, that)
}

object Cause extends Serializable {
  case object Empty extends Cause[Nothing]
  final case class Fail[+E](value: E, override val trace: Trace) extends Cause[E]
  final case class Die(value: Throwable, override val trace: Trace) extends Cause[Nothing]
  final case class Interrupt(fiberId: FiberId, override val trace: Trace) extends Cause[Nothing]
  final case class Stackless[+E](cause: Cause[E], stackless: Boolean) extends Cause[E]
  final case class Then[+E](left: Cause[E], right: Cause[E]) extends Cause[E]
  final case class Both[+E](left: Cause[E], right: Cause[E]) extends Cause[E]
}
  • ZIO상에서 에러의 처리를 위해서는 아래의 관련 함수를 숙지해야 한다.
    • .catchAll
    • .catchSome
    • .either / .absolve
      • [R, E, A] --> [R, Either[E, A]]
      • [R, Nothing, Either[E, A]] --> [R, E, A]
    • .absorb  // defect to Failures (recover from both Die and Interruption)
      • [Any, Nothing, Nothing] --> [Any, Throwable, Nothing]
    • .resurrent // defect to Failures (recover from only from Die) 
      • [Any, Nothing, Nothing] --> [Any, Throwable, Nothing]
    •  .orDie
      • [R, Throwable, A] --> [R, Nothing, A]
    • .refineOrDie // defect to Failures (narraw down the type of the error channel from E)
      • [R, Throwable, A] --> [R, IOException, A]
    • .unrefine // defect to Failures (broadens the type of the error channel from E to the E1 and embeds some defects into it)
      • [R, Nothing, A] --> [R, E, A]
    • .sandbox (.unsandbox) // defect to Failures
      • [R, Nothing, A] --> [R, Cause[E], A]
    • .ensuring : catch block과 유사한 역할
      • [R, E, A] --> [R, ???, A]
    • .some
      • [R, E, Option[A]] --> [R, Option[E], A]
    • .cause (.uncause)
      • [R, E, A] --> [R, Nothing, Cause[E]]
    • .merge
      • [R, E, A] --> [R, super(A|E)]
    • .reject : success chanel의 값의 일부를 fail channel로 처리
      • [R, E, A] --> [R, E, A]

 

 

 

'Tech > ZIO' 카테고리의 다른 글

Scala ZIO의 error handling - (1)  (0) 2024.05.05

+ Recent posts