【问题标题】:Better handling of nested Options更好地处理嵌套选项
【发布时间】:2019-02-28 12:22:39
【问题描述】:

考虑一个嵌套结构,其中相关属性如下:

case class Validation { sql: Option[SqlDataSource] }

case class SqlDataSource { dfh: Option[DataFrameHolder] }

case class DataFrameHolder { sql: Option[String] }

我目前处理这个的天真的方式是:

    val ssql = expVal.sql.getOrElse(
        vc.dfh.map(_.sql
          .getOrElse(throw new IllegalStateException(s"$logMsg CompareDF: Missing sql container"))
        ).getOrElse(throw new IllegalStateException(s"$logMsg CompareDF: dfh missing sql"))
      .sql.getOrElse(throw new IllegalStateException(s"$logMsg CompareDF: Missing sql")))

虽然这确实完成了工作,但它也对读者不友好且对开发人员不友好(很难正确嵌套)。有什么更好的处理方法的想法吗?

更新感谢您的出色回答 - 这将有助于清理和简化前进的异常处理代码。

【问题讨论】:

  • vc 来自哪里? $logMsg 是在哪里组成的?

标签: scala


【解决方案1】:

如果你想要快速失败的语义,我会使用 for comprehensionTrys。

final case class Validation(ql: Option[SqlDataSource])
final case class SqlDataSource(dfh: Option[DataFrameHolder])
final case class DataFrameHolder(sql: Option[String])

val expVal = Validation(
    ql = Some(
      SqlDataSource(
        dfh = Some(
          DataFrameHolder(
            sql = Some("Hello, World!")
          )
        )
      )
    )
  )


implicit class OptionOps[T](private val op: Option[T]) {
  def toTry(ex: => Throwable): Try[T] = op match {
    case Some(t) => Success(t)
    case None    => Failure(ex)
  }
}

val ssql: Try[String] = for {
  ql <- expVal.ql.toTry(new IllegalStateException("CompareDF: Missing sql container"))
  dfh <- ql.dfh.toTry(new IllegalStateException("CompareDF: dfh missing sql"))
  sql <- dfh.sql.toTry(new IllegalStateException("CompareDF: Missing sql"))
} yield sql

【讨论】:

  • 也很有用 - 并且不需要额外的库
  • @javadba 是的,但老实说我更喜欢 Cats 解决方案,特别是因为您可以使用 ValidatedNel 来累积所有错误,并且 Either 比 Try 更好。另外,作为一个建议,尽量避免抛出异常。
  • “避免抛出异常”对此没有一致意见。抛出异常在我们的生产环境中效果很好,可以冒泡到日志记录和警报机制。 最糟糕的事情是EAT例外
  • 哦,对不起,我应该对此添加一个更好的解释 - 但我很着急,我的错。好吧,我不是说不应该在你的程序中报告错误,更不用说你不应该处理它们。我的意思是你不应该抛出它们——作为一种副作用,而是使用EitherTry 以类型安全的方式封装你的错误——应该抛出的唯一异常是真实的FATAL 错误,例如 StackOverflowOutOfMemory,BTW 不应该被捕获,而是让它们使应用程序崩溃。见this
【解决方案2】:

Luis Miguel Mejía Suárez 和 Xavier Guihot 的答案都很好,但没有必要构建自己的 toTry() 方法。 Either 已经提供了一个,而且由于Either 也是右偏的,所以它可以用于理解。

import util.Try

val ssql: Try[String] = (for {
  ql  <- expVal.ql.toRight(new IllegalStateException("yada-yada"))
  dfh <- ql.dfh   .toRight(new IllegalStateException("yoda-yoda"))
  sql <- dfh.sql  .toRight(new IllegalStateException("yeah-yeah"))
} yield sql).toTry

【讨论】:

  • 鉴于此人坚持使用 vanilla scala(plus 虽然不是必须)并且完全符合我的要求 - 我重新- 在这里分配奖励。
  • 这实际上是一个很好的。我刚刚注意到 Either 自 2.12 以来现在是右偏的,而在早期版本中并非如此。顺便说一句,我认为答案中应该是Either 而不是Try,这可能是一个错字?
  • @BinziCao; Either 被强制转换为 Try(通过 toTry),以便返回类型与我引用的其他答案一致。如果效果更好,OP 可以将其保留为 Either
【解决方案3】:

我会建议与Binzi Cao's answer 非常相似的解决方案,但只使用带有scala.util.Try monad 的标准库:

def toTry[T](x: Option[T], message: String): Try[T] =
  x.map(Success(_)).getOrElse(Failure(new IllegalStateException(message)))

(for {
  sqlDataSource   <- toTry(validation.sql, s"Missing sql container")
  dataFrameHolder <- toTry(sqlDataSource.dfh, s"dfh missing sql")
  sql             <- toTry(dataFrameHolder.sql, s"Missing sql")
} yield sql)
.get

for-comprehension 生成 Try。如果我是Success[String],则在生成的Try 上应用.get 将返回TrydataFrameHolder 内的String)的内容,或者如果它是Failure[IllegalStateException],则抛出异常。

【讨论】:

  • 不错的解决方案 - 并且赞成 - 我必须选择 one 并选择来自@jwvh的较短的
【解决方案4】:

您可能希望使用cats 库来使您的代码更实用。您可以将Option 转换为Either,如下所示:

import cats.implicits._

 def toEither[T](s: Option[T], error: String) = {
    s.liftTo[Either[String, ?]](error)
  }

  def runEither = {

    val result =
      for {
        sqlDataSource   <- toEither(validation.sql, s"Missing sql container")
        dataFrameHolder <- toEither(sqlDataSource.dfh, s"dfh missing sql")
        sql             <- toEither(dataFrameHolder.sql, s"Missing sql")
      } yield sql
    result match {
      case Right(r) => r
      case Left(e)  => throw new Exception(e)
    }
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 2017-06-14
    • 1970-01-01
    相关资源
    最近更新 更多