2
2

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 3 years have passed since last update.

ScalaでEitherを利用したエラー処理のユースケース

Posted at

Scalaではエラー処理に例外ではなくEitherを使うのが良いという話を聞いたのですが、実用ケースがうまく想像できなかったので、書いてみました。
※ あくまで私の考えた結果なので、ベストプラクティスであるかはわかりません。

参考

下記資料を参考にさせていただきました。
https://scala-text.github.io/scala_text/error-handling.html

例外処理について

一般にエラーが起きたときの挙動を制御するときは、例外をthrowすることが一般的だと思います。しかし、例外には下記のような問題点があります。

  • 処理の流れがわかりづらい
  • 非同期プログラミングで扱いにくい
  • 型チェックが動作しない

そのため、Scalaでは例外を投げるのではなく、メソッドの返り値としてエラーを表現することが推奨されています。

Eihterとは

エラーを表現することができる型の一つです。
LeftかRightいずれかの値をとるような構造になっており、エラー処理で利用する場合は、Leftにエラー内容、Rightに正常時の返り値を格納するようにして使います。
map, flatmap, forなどを利用することにより、Left, Rightどちらであっても適用できるような処理を書くことができるので、メインの処理から、エラー対応のロジックを切り離すことができます。

Eitherによるエラー処理の例

具体的なユースケースを見るために、
下記の処理フローを備えたプログラムを想定します。

  1. データを読み込む
  2. データを加工する
  3. データを書き込む

各ステップにおいて、読み込みエラー、加工エラー、書き込みエラーが想定されます。
以下、Eitherを利用したエラー処理の例となります。
(具体の処理メソッドの記述は省略しています)

まず、各処理のエラー型を定義します。

sealed trait ProgramError
case object ReadDataError extends ProgramError
case object ProcessDataError extends ProgramError
case object WriteDataError extends ProgramError

各処理からEither型の値を返すインターフェースを作成します。

def readData(read: () => String): Either[ProgramError, String] = {
    """
    read: データ読み込み用の関数
    """
    try {
        Right(read())
    } catch {
        case e: Exception => {
            Left(ReadDataError)
        }
    }
}

def ProcessData(process: () => String): Either[ProgramError, String] = {
    """
    process: データ加工用の関数
    """
    try {
        Right(process())
    } catch {
        case e: Exception => {
            Left(ProcessDataError)
        }
    }
}


def writeData(write: () => Unit): Either[ProgramError, Unit] = {
    """
    write: データ書き込み用の関数
    """
    try {
        Right(write())
    } catch {
        case e: Exception => {
            Left(WriteDataError)
        }
    }
}    

具体の処理プログラムは下記のようになります。

val inputData: Either[ProgramError, String] = readData(
    readSomething()
)

val processdData: Either[ProgramError, String] =  inputData.fold(
        l => Left(l),
        r => processData(processSomething(r))
)

val result: Either[ProgramError, Unit] = processdData.fold(
    l => Left(l),
    r => writeData(writeSomething(r)) 
)

// 発生したエラーに対応したメッセージを出力する。
result match {
    case Left(ReadDataError) => System.err.println("ReadDataError occured")
    case Left(ProcessDataError) => System.err.println("ProcessDataError occured")
    case Left(WriteDataError) => System.err.println("WriteDataError occured")
}

foldを利用することで、値がRight, Leftそれぞれの場合で処理を分岐させることができます。
Eihterの型を保ったまま、処理を続けていることがわかるでしょうか。
このような形式にすることで、各処理の途中でエラー対応を実施するのではなく、最後にまとめて扱うことができるようになります。

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?