3

我了解 Some/None/Option 背后的整个原理和概念,我当然可以欣赏它的优点。我的问题更多的是最佳实践。它什么时候会变得矫枉过正,什么时候虔诚地使用它才有意义。我认为(我可能是错的)但尽可能多地使用它是有意义的,因为它是一种更安全的方式来传递任何东西(而不是 null)。我看到自己经常做的事情是让一些函数散布着 map、getOrElse、get、match 甚至有时嵌套它们,这往往看起来很难看。是否有一些我遗漏的概念或一些与接收多个可选值的函数有关的最佳实践。例如:

  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)
  }

我设法重构了一些看起来不那么疯狂,但是有没有更好的方法来重构它,让它看起来不那么疯狂?我是不是错过了什么,或者你已经习惯了看起来像这样的东西?似乎肯定应该有更清洁的做法。

4

2 回答 2

8

由于 Option 类是一元的,因此您应该使用推导for来使代码看起来更清晰。例如,您的第二个示例可以重写为:

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
于 2013-10-18T15:18:10.450 回答
0

使用 for-comprehensions 或至少使用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
}

这种方法或 for 理解是否更有效取决于(根据我的经验)您必须进行多少中间处理。如果你能把所有的东西都写成一条线,那么理解就很好而且很干净。一旦你需要一些更复杂的东西,我更喜欢这种方法。

请注意,for 推导是规范的 Scala 构造,而这需要新用户进行一些学习。因此,支持人们可能需要快速上手的代码中的理解或 flatMaps。

于 2013-10-19T00:50:02.173 回答