将尝试转换为选项而不丢失 Scala 中的错误信息

Convert try to option without losing error information in Scala

简介

我创造了一个可爱的单线:

Option("something").map(_ => Try("something else")).flatten.getOrElse("default")

实际上没有编译,错误:

Error:(15, 31) Cannot prove that scala.util.Try[String] <:< Option[B].
Option("").map(_ => Try("")).flatten.getOrElse("");}
                             ^

所以我找到了解决方法:

Option("something").flatMap(_ => Try("something else").toOption).getOrElse("default")

但是,问题

我的同事警告我,我的构造实际上正在丢失错误信息。这是事实,但在实际应用中 - 是不可接受的。

摆脱所有重复后我得到了:

implicit class CoolTry[T](t: Try[T]) extends StrictLogging {
  def toOptionSE: Option[T] = t match {
    case Success(s) => Some(s)
    case Failure(ex) =>
      logger.error(ex.getMessage, ex)
      None
  }
}

使用:

Option("something").flatMap(_ => Try(new Exception("error")).toOptionSE).getOrElse("default")

问题

我相信每个应用程序中都有很多类似的情况,我只是不知道是我的方法不好还是 Try().toOption 只是做错了?

我知道日志记录是一个副作用,但是在使用 Try 时我想每个人 是否 预料到如果出现问题?

谢谢!

每次将 Try[T] 更改为 Option[T] 时强制记录是 IMO 的不良影响。当您进行这样的转换时,您明确承认您并不真正关心故障的内部结构,如果它发生了。您想要的只是访问结果(如果存在)。大多数时候你说 "well, this is undesirable, I always want to log exceptions",但有时你只关心最终结果,所以处理 Option[T] 就足够了。

Or what (other) approach should I take here?

在这里使用 Either[A, B] 是一个选项,特别是在 Scala 2.12 中,当它变得右偏时。您可以简单地映射它并仅在最后检查是否有错误然后记录(使用 Scala 2.12.1 创建):

val res: Either[Throwable, String] = Option("something")
  .map(_ => Try("something else").toEither)
  .getOrElse(Right("default"))
  .map(str => s"I got my awesome $str")

理想情况下 (IMO) 将日志记录的副作用推迟到最后可能的时间点会更好。

一个可能的改进是让用户crystal清楚正在发生的事情;在 implicit 被编译器注入某处和明显的 "pure" 名称 (toOptionSE) 之间,对于第二个阅读 and/or 修改的开发人员来说,可能不清楚发生了什么你的代码。此外,您正在修复处理错误情况的方式,不要留下以不同于记录错误的方式处理它的机会。

您可以利用投影来处理错误,例如在 Try 上定义的 failed 投影。如果你真的想在一行中流畅地完成这项工作,你可以像这样利用 implicit classes。

implicit class TryErrorHandlingForwarding[A](t: Try[A]) {
  def onError(handler: Throwable => Unit): Try[A] = {
    t.failed.foreach(handler)
    t
  }
}

// maybe here you want to have an actual logger
def printStackTrace: Throwable => Unit =
  _.printStackTrace

Option("something").
  flatMap(_ => Try(???).onError(printStackTrace).toOption).
  getOrElse("default")

此外,我假设无论出于何种原因,您不能从一开始就使用 Try(正如评论中所建议的那样)。