何时使用Option / None返回类型

时间:2013-10-18 15:08:16

标签: scala scala-2.10

我理解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)
  }

我设法重构一些看起来不那么疯狂,但有没有更好的方法来重构这个看起来不那么疯狂?我错过了什么或你是否习惯了这样的事情?似乎应该有一个更清洁的做法。

2 个答案:

答案 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。