我理解Some / None / Option背后的整个原则和概念,我当然可以理解它的优点。我的问题更多是最佳做法。什么时候变得矫枉过正,什么时候有意义地使用它。我认为(我可能是错的)但是尽可能多地使用它是有道理的,因为它是一种更安全的方式来传递任何东西(而不是null)。我看到自己做了很多事情,虽然有一些功能乱七八糟的地图,getOrElse,get,match甚至有时嵌套它们往往看起来很难看。是否有一些我缺少的概念或一些最佳实践与接收多个Optional值的函数有关。例如:
def updateJobs(id: Int) = withAuth {
request => {
User.userWithToken(request.headers.get("token").get).map {
user =>
Job.jobsAfterIdForForeman(id.toString, user.id.toString) match {
case Some(json) => Ok(json)
case _ => NoContent
}
}.getOrElse(BadRequest)
}
}
甚至更糟糕的例如:
def addPurchaseRequest(json: JsValue) = {
(json \ "jobId").asOpt[Long].map {
jobId => JobDAO.jobWithId(jobId).map {
job => PurchaseRequestDAO.insert(new PurchaseRequest(json, job)).map {
model =>
val request = model.asInstanceOf[PurchaseRequest]
(json \ "items").asOpt[List[JsObject]].map {
list => {
if (PurchaseItemAssociationDAO.bulkInsert(PurchaseItemAssociation.itemsFromJsonArray(list, request.id))) Option(request.addResponseJson) else None
}
}.getOrElse(None)
}.getOrElse(None)
}.getOrElse(None)
}.getOrElse(None)
}
我设法重构一些看起来不那么疯狂,但有没有更好的方法来重构这个看起来不那么疯狂?我错过了什么或你是否习惯了这样的事情?似乎应该有一个更清洁的做法。
答案 0 :(得分:8)
由于Option类是monadic,因此您应该使用for
comprehension来使代码看起来更干净。例如,您的第二个示例可以重写为:
def addPurchaseRequest(json: JsValue) =
for {
jobId <- (json \ "jobId").asOpt[Long]
job <- JobDAO.jobWithId(jobId)
model <- PurchaseRequestDAO.insert(new PurchaseRequest(json, job))
request = model.asInstanceOf[PurchaseRequest]
list <- (json \ "items").asOpt[List[JsObject]]
if PurchaseItemAssociationDAO.bulkInsert(PurchaseItemAssociation.itemsFromJsonArray(list, request.id))
} yield request.addResponseJson
答案 1 :(得分:0)
使用for-comprehension或至少使用flatMap
代替map / getOrElse(None)
来使事情变得更紧凑。此外,习惯上将新变量放在前一行的末尾而不是它自己的行。这将极大地清理你的最坏情况。
或者,对于需要中间逻辑的特别长的链,我发现类似异常的机制比for-comprehensions更好(基于与非本地返回相同的原理):
trait Argh extends scala.util.control.ControlThrowable
implicit class GetOrArgh[A](val underlying: Option[A]) extends AnyVal {
def or(a: Argh) = underlying match {
case None => throw a
case _ => underlying.get
}
}
def winnow[A](f: Argh => A): Option[A] = {
val argh = new Argh { }
try { Some(f(argh)) }
catch { case x: Argh if (x eq argh) => None }
}
然后你可以这样使用:
def addPurchaseRequest(json: JsValue) = winnow { fail =>
val jobId = (json \ "jobId").asOpt[Long] or fail
val job = JobDAO.jobWithId(jobId) or fail
val model = PurchaseRequestDAO.insert(new PurchaseRequest(json, job)) or fail
val request = model match {
case pr: PurchaseRequest => pr
case _ => throw fail
}
val list = (json \ "items").asOpt[List[JsObject]]
if (!PurchaseItemAssociationDAO.bulkInsert(
PurchaseItemAssociation.itemsFromJsonArray(list, request.id)
)) throw fail
request.addResponseJson
}
这种方法或理解能否更好地取决于(根据我的经验)你需要做多少中间处理。如果你能把所有东西都做成单行,那么理解就是美好而干净。一旦你需要更纠结的东西,我更喜欢这种方法。
请注意,对于理解是规范的Scala构造,而这需要对新用户进行一些学习。因此,有利于人们可能需要快速加速的代码中的理解或flatMaps。