我同意 Edmondo 关于使用理解的建议,但不同意关于使用验证库的部分(至少不再考虑到 2012 年以来添加到 scala 标准库的新功能)。根据我使用 scala 的经验,在使用标准库时努力想出好的声明的开发人员最终也会在使用像猫或 scalaz 这样的库时做同样的事情,甚至更糟糕。也许不在同一个地方,但理想情况下我们会解决问题,而不仅仅是移动它。
这是用理解重写的代码,或者是 scala 标准库的一部分:
def save() = CORSAction { request =>
// Helper to generate the error
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
//Actual validation
val updateEither = for {
json <- request.body.asJson.toRight(badRequest("Expecting JSON data"))
feature <- json.asOpt[Feature].toRight(badRequest("Invalid feature entity"))
rs <- MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.toRight(badRequest("You have already reached the limit"))
} yield toJson(feature.update).toString
// Turn the either into an OK/BadRequest
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
解释
错误处理
我不确定您对此了解多少,但它们在行为上与 Edmondo 提供的 Validation 或 scala 库中的 Try 对象非常相似。这些对象之间的主要区别在于它们的能力和错误行为,但除此之外,它们都可以以相同的方式进行映射和平面映射。
您还可以看到,我使用toRight立即将选项转换为 Either,而不是在最后执行。我看到 java 开发人员有反射性地在物理上尽可能地抛出异常,但他们这样做主要是因为 try catch 机制很笨拙:如果成功,要从 try 块中获取数据,您要么需要返回它们或将它们放入块中初始化为 null 的变量中。但 scala 并非如此:您可以映射 try 或 any,因此通常情况下,如果您在将结果识别为错误表示后立即将其转换为错误表示,您将获得更清晰的代码,因为它们被识别为不正确。
为了理解
我也知道,发现 scala 的开发人员常常对理解感到困惑。这在大多数其他语言中是很容易理解的,因为它仅用于集合的迭代,而 scala 似乎可用于许多不相关的类型。在 scala 中,调用 flatMap 函数实际上是更好的方法。编译器可能会决定使用 map 或 foreach 对其进行优化,但它仍然正确假设您在使用 for 时将获得 flatMap 行为。在集合上调用 flatMap 的行为就像在其他语言中的 for each 一样,因此 scala for 可以像处理集合时的标准一样使用。但是您也可以在任何其他类型的对象上使用它,这些对象提供了具有正确签名的 flatMap 实现。如果你的 OK/BadRequest 也实现了 flatMap,
因为人们对在看起来不像集合的任何东西上使用 for 感到不自在,以下是如果明确使用 flatMap 而不是 for 时函数的外观:
def save() = CORSAction { request =>
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
val updateEither = request.body.asJson.toRight(badRequest("Expecting JSON data"))
.flatMap { json =>
json
.asOpt[Feature]
.toRight(badRequest("Invalid feature entity"))
}
.flatMap { feature =>
MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.map(_ => feature)
.toRight(badRequest("You have already reached the limit"))
}
.map { rs =>
toJson(feature.update).toString
}
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
请注意,就参数范围而言,如果函数是嵌套的,而不是链接的,则行为会生效。
结论
我认为,除了不使用正确的框架或正确的语言功能之外,您提供的代码的主要问题是如何处理错误。一般来说,你不应该写错误路径,因为你认为你在方法结束时会堆积起来。如果您可以在错误发生时立即处理它们,那么您就可以转移到其他地方。相反,你把它们推回去的次数越多,你就会有越多的代码具有不可分割的嵌套。它们实际上是 scala 期望您在某个时候处理的所有未决错误案例的具体化。