使用较少的匹配子句处理 scala 中的自定义错误的最干净和最好的方法

cleanest & best way to handle custom error in scala with lesser match clauses

我正在尝试找到处理 scala 中自定义错误的最佳和最简洁的方法。

目前我有一个通用的全局错误对象(我的代码看起来与此类似):

sealed trait AppError {
  def message: String
}

object AppError {

  /* Cat Error Messages */
  case class CatNotFound(id: Long) extends AppError {
    val message = Messages("cat.not_found").format(id)
  }
  case class CatNotForUser(id: Long) extends AppError {
    val message = Messages("cat.not_for_user").format(id)
  }

}

每个方法都是 returning Either[<SomeValidType>, AppError](我在右侧发送错误)例如:

def getCatForUser(id: Long, user: User): Either[Boolean, AppError] = {
    CatService get(id) match {
      case None => CatNotFound(id)
      case Some(x) =>
        CatService isAccessibleByUser(x, user) match {
          case false => CatNotForUser(id)
          case true => true
        }
    }
  }

调用 validation/get 部分的方法有很多嵌套 match 像这样:

def something(id: Long, user: User) = {
  getCatForUser(id, user) match {
    case Left(b) =>
       anotherMethodReturnsEither(id) match {
         case Left(x) =>
            thereCanBeLotOfNesting(user) match {
              case Left(y) =>
                // do something or create another nesting call
              case Right(e) =>
                handleSomeHow(e)
            }
         case Right(e) =>
            handleSomeHow(e)
        }
    case Right(e) =>
      handleSomeHow(e)
  }
}

def handleSomeHow(e: AppError) = {
     e match {
        case CatNotFound(_) => NotFound(e.message)
        case CatNotForUser(_) => Unauthorized(e.message)
     }
}

在一个项目中,我使用 CustomExceptions 来处理其他 nesting/if。在那种情况下,我会抛出错误,这些错误会传播到顶部,在那里它们可以一起处理。 当我们使用异常而不是错误时,代码看起来很干净。但我认为当您验证数据并且有这样的方法时,异常是一个问题 return 除了 throw Exception:

def validateCatForUser(id: Long, user: User) = {
    CatService get(id) match {
      case None => throw CatNotFound(id)
      case Some(x) =>
        CatService isAccessibleByUser(x, user) match {
          case false => throw CatNotForUser(id)
          case true => // nothing can be returned as its validation
        }
    }
  }

我会像这样使用它:

def something(id: Long, user: User) = {
  validateCatForUser(id, user)
  /*
   * here will be some logic after validation
   */
}

仍然清晰可读。所以我的问题是我应该使用什么来实现具有更少 match 具有 Left() or Right() 的子句的更具可读性的代码。我正在阅读有关 scala 验证的信息,发现 http://blog.lunatech.com/2012/03/02/validation-scala 但 scala 'Validation' 也 returns ValidationNEL[String, <some valid type>] 不会减少 match 子句。在我看来,处理自定义错误的最佳方法是?

您应该切换到 Scalaz 的 \/(我称之为 'Scalaz Either' 或 'Or')并将您的 'happy path' 移到右侧。

\/ 是偏右的,这使您可以在 for-comprehensions 中使用它。你可以这样写:

for {
  resA <- computeA()
  resB <- computeB(resA)
} yield resB

其中 computeX return \/[Error, X].

在使用 Either 时,

for 理解确实有助于解决 readability/nesting 问题。您可以使用 leftright 来投影 Eithers 并产生一个 Either 来收集错误:

val x = Left(5) : Either[Int, String]
val y = Right("error") : Either[Int, String]
val z = Right("error") : Either[Int, String]

for {
   xval <- x.left
   yval <- y.left
   zval <- z.left
 } yield zval

使用此语法可以使您的嵌套版本更加简洁。它可能看起来像这样:

def something(id: Long, user: User) = {
    for {
        cat <- getCatForUser(id, user).left
        otherValues <- anotherMethodReturnsEither(id).left
        noNesting <- thereCanBeLotOfNesting(user).left
    } yield noNesting
} match {
    case Left(value) => // do something
    case Right(e) => handleSomeHow(e)
}