简体   繁体   English

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

[英]Convert try to option without losing error information in Scala

Introduction 介绍

I had created a lovely one-liner: 我创造了一个可爱的单行:

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

which actually does not compile, with error: 实际上没有编译,有错误:

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

so I had found a way around: 所以我找到了解决方法:

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

But, the problem 但是,问题

My colleague warned me, that my construction is actually loosing the error information. 我的同事警告我,我的构造实际上是在丢失错误信息。 This is true, and in real-life application - not acceptable. 这是真的,在现实生活中应用 - 不可接受。

After getting rid of all the repetition I had ended up with: 在摆脱了我最终的所有重复之后:

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

using: 使用:

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

Question

I believe there are many similar cases in every application and I simply do not know if either my approach is bad or Try().toOption is simply done wrong? 我相信在每个应用程序中都有很多类似的情况,我根本不知道我的方法是坏还是Try().toOption简单地做错了?

I understand that logging is a side effect, but while using Try I guess everyone does expect it if something goes wrong? 据我所知,伐木是一个副作用,但在使用Try我想每个人都不会想到,如果出了问题?

  • Can the implicit class be improved? implicit类可以改进吗?
  • Is there any utility library handling Try().toOption my way? 是否有任何实用程序库处理Try().toOption我的方式?
  • Or what (other) approach should I take here? 或者我应该采取什么(其他)方法?

Thanks! 谢谢!

Forcing logging every time you change a Try[T] to an Option[T] is an undesired effect IMO. 每次将Try[T]更改为Option[T]时强制记录都是IMO的不良影响。 When you do such a transformation you're explicitly admitting that you don't really care about the internals of the failure, if it happened. 当你进行这样的转换时,你明确承认你并不真正关心失败的内部,如果它发生的话。 All you want is to access the result, if it exists. 您只需要访问结果(如果存在)。 Most of the time you say "well, this is undesirable, I always want to log exceptions", but sometimes you simply only care about the end result, so dealing with an Option[T] can be good enough. 大多数时候你说“好吧,这是不可取的,我总是想记录异常”,但有时你只关心最终结果,所以处理Option[T]就足够了。

Or what (other) approach should I take here? 或者我应该采取什么(其他)方法?

Using Either[A, B] is an option here, especially in Scala 2.12 when it became right biased. 使用Either[A, B]是一个选项,特别是在Scala 2.12中,当它变得正确偏向时。 You can simply map over it and only at the end check if there was an error and then log (created with Scala 2.12.1): 您可以简单地映射它,并且只在最后检查是否存在错误,然后记录(使用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")

Ideally (IMO) deferring the logging side effect to the last possible point would serve you better. 理想情况下(IMO)将记录副作用推迟到最后可能的点将更好地为您服务。

One possible improvement would be to make it crystal clear to the user of what is going on; 一个可能的改进是使用户清楚地了解正在发生的事情; between the implicit being injected somewhere by the compiler and the apparently "pure" name ( toOptionSE ) it may not be evident of what is going on for a second developer reading and/or modifying your code. 在编译器implicit注入和明显“纯粹”名称( toOptionSE )之间,第二个开发人员阅读和/或修改代码的内容可能并不明显。 Furthermore, you're fixing how you treat the error case and don't leave the opportunity to handle it differently from logging it. 此外,您正在修复如何处理错误情况,并且不会留下以记录它的方式处理它的机会。

You can treat errors by leveraging projection, like the failed projection defined over Try . 您可以通过利用投影来处理错误,例如在Try定义的failed投影。 If you really want to do this fluently and on one line, you can leverage implicit class es like this. 如果你真的想要在一行中流利地做到这一点,你可以利用这样的implicit class

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

Also, here I'm assuming that for whatever reason you cannot use Try right from the start (as it's been suggested in a comment). 此外,在这里我假设无论出于什么原因你都不能使用从一开始就Try (因为它已在评论中提出)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM