使用较少的匹配子句处理 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 问题。您可以使用 left
和 right
来投影 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)
}
我正在尝试找到处理 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 问题。您可以使用 left
和 right
来投影 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)
}