scalazのValidationの使い方 値の検証・エラー処理


今回はscalazのValidationの話です。これを使って引数のチェック処理、いわゆるバリデーションを行います。

対象メソッド

今回はこのようなメソッドのバリデーションを考えます。まずはバリデーション無し版です。単に受け取った引数からPersonオブジェクトを作成して、画面に出力するだけです。

object Main extends App {
  someMethod("name", 17)

  def someMethod(name: String, age: Int) {

    val person = Person(name, age)
    println(person)
  }
}

case class Person(name: String, age: Int)

※mainメソッドが無いですが、これはAppを継承しているため、書かなくても実行できるようになっているため、書いていません。

愚直に実装した版

object Main extends App {
  someMethod("name", 17)

  def someMethod(name: String, age: Int) {

    if (!name.contains(" ")) {
      println("Error:名前がスペースで区切られていません")
    } else if (age < 18) {
      println("Error:未成年です")
    } else {
      val person = Person(name, age)
      println(person)
    }
  }
}

case class Person(name: String, age: Int)

ifで分岐してチェックしています。こういう処理がいわゆるバリデーションです。

この実装だと少し問題があります。コードを見る限り名前には半角スペースが入っており、なおかつ年齢が18以上でないといけないという要件が伺えます。実引数はこれら2つの要件を両方とも満たしていないにも関わらず、else ifのせいでエラーメッセージは1つしか表示されません。

じゃあelse ifを取っ払ってエラーメッセージをListに詰めればいいんじゃないの!となります。実際そうする人は多いかと思います。

エラーリストが空なら正常処理、空でないなら異常処理、みたいな。

実はそういうことをするならscalazのValidationを使うといい感じに書けます。

scalaz.Validationを使ったバリデーション

import scalaz.Scalaz._
import scalaz._

object Main extends App {
  someMethod("name", 17)

  def someMethod(name: String, age: Int) {

    validatePerson(name, age) match {
      case Success(a) =>
        println(a)
      case Failure(e) =>
        println(e)
    }
  }

  def validatePerson(name: String, age: Int): ValidationNel[String, Person] = {
    val validatedName: ValidationNel[String, String] =
      if (!name.contains(" ")) "Error:名前がスペースで区切られていません".failureNel[String] else name.successNel[String]
    val validatedAge: ValidationNel[String, Int] =
      if (age < 18) "Error:未成年です".failureNel[Int] else age.successNel[String]

    (validatedName |@| validatedAge)(Person)
  }
}

case class Person(name: String, age: Int)

1つvalidatePersonメソッドを追加しました。これが値の検証とオブジェクト生成を行っています。

受け取った側ではmatchで分岐しています。

実行結果は以下のような感じです。

# 実行例

# エラーがある場合の出力
NonEmptyList(Error:名前がスペースで区切られていません, Error:未成年です)

# 正常の場合の出力
Person(first last,18)

処理概要

failureNelかsuccessNelでValidationを生成しています。Validation自体はtraitなので直接生成することはできません。

NelはNotEmptyListの略です。これを使うとエラーメッセージをListで持っておくことができます。

今回はメソッドの戻り値をValidationNel[String, Person]で固定しているので、エラーメッセージをStringではなく、ExceptionとかにしたいのであればValidationNel[Throwable, Person]みたいに適宜変えてください。

matchで受ける側もそのまま処理続けるのかthrowするのかなど、ケースバイケースで変える必要がありますね。

Validationを日本語で解説した記事やValidationオブジェクトを生成する解説があまり無かったので書いてみました。みなさんもValidation使いましょう!