在公共/开源Scala API中使用Try或Either进行错误处理是否存在问题?

时间:2016-03-14 15:59:44

标签: scala

我注意到许多开源Scala API使用Future(如Slick)或Java风格的异常处理(如spray-json)而不是TryEither

在没有长时间运行或异步的简单开源API中返回TryEither是否存在问题?我应该避免这些,如果是的话,我应该使用什么呢?

(显然,我更倾向于使用核心Scala API而不是第三方库,例如Scalaz,以避免迫使下游用户也使用这些库。)

3 个答案:

答案 0 :(得分:4)

Try适用于非异步操作的包装异常 但是,我认为使用异常实例返回非异常错误条件不是正确。当API是异步的并使用Future时,我也是这样。 将异常错误与业务错误混合在一起很容易导致API客户端(也就是我:)的错误处理不正确/不足。

如果您决定仍使用例外来表示您的业务错误,请记住异常带有隐藏价格:构建堆栈跟踪。如果您创建自己的业务例外并混合scala.util.control.NoStackTrace,则可以减轻这种情况。

Scala标准库中的默认Either实现是无偏的,这意味着区分成功和失败的情况仅依赖于约定:

  

右是正确的(正确),因此左错(错误)。

不偏不倚的一面也让人很难跟上"快乐"路径(或使错误恢复脱颖而出)

此时您的最佳选择(scala 2.11.6和相应的标准库)将根据您的依赖关系和您希望向用户公开的API而有所不同,但它不会来自scala标准库。

    如果您已经严重依赖scalaz
  • Scalaz Validation \/,则可以选择
  • Scalactic's Or typeAccord
  • Cat's Xor是一个不错的轻量级解决方案,如果你不想引入整个scalaz。它是成熟的,是从最新的
  • 中提取出来的
  • (来自评论)modes可能是一个选项,但我不太了解它与其他人进行比较。
  • (来自评论)parboiled2可能是一个选项,但我不太了解它与其他人进行比较。
  • Rapture slip可以允许您让API的客户端决定他想要取回的类型(例如,在expert group call中使用此方法)。我不知道图书馆作者或编译时的成本是多少。

您可能已经依赖于提供自己的validation类型的库。例如,play-json有一个JsResult类型,其验证类似于能力。根据您的工作重复使用类型可能是有意义的。

正在进行{{3}},并且{{3}}可以将偏向的任一类型添加到Scala中的标准库中。上述两种解决方案具有已经能够累积错误的额外好处。

答案 1 :(得分:2)

在以下情况下返回Try [T]:在正确结果和错误结果(即错误)之间存在语义差异。这是因为:

  • 尝试是偏右的(它适用于地图,平面地图和for-comprehensions)。

  • 失败案例是一个Throwable,这很重要,因为throwable总是清楚地表明某些事情是错误的,甚至超出了代码的边界。

  

警告:有时堆栈跟踪生成是一种开销,但并非总是如此!在过早优化代码之前要三思而后行:堆栈跟踪带有许多有用的调试信息。   如果你以后发现你真的需要跳过堆栈跟踪生成,没问题:如上所示使用with NoStackTrace

def validate(s:String):Try[String] = Option(s).filter(!_.isEmpty).map(Try(_)).getOrElse(new Exception("bad input") with NoStackTrace 

时使用[L,R]:两种情况都可以接受,但它们是互斥的(即[Integer,Float])。出于这个原因,尽管Scalaz的家伙不得不对此进行评论,但我相信这两者都是公正的。

def parseNum(s:String):Either[Int,String] = Try(parseInt(s)).map(Left(_)).getOrElse(Right(s))

使用时的元组类似于Either,但是当你有N时,非互斥的值(你知道所有这些都有用)

def parseAndReturn(s:String):(Integer,String) = (parseInt(s), s) 

T可以为空时,或当您处于与Try [T]相同的情况时,返回选项[T],但是您不能给出关于为什么出错了。

def attemptToparse(s:String):Option[Result] = Try(parse(s)).toOption

答案 2 :(得分:0)

我同意Try用于捕获/通知异常,而不是用于返回业务/验证错误。 Scalaz是一个非常好的选择,但是如果你不想依赖它,我认为Either是好的,不偏不倚是烦人但不是交易破坏者恕我直言: 一旦你建立了Right是有效结果的约定,你就可以"正确的偏见"它与.rightProjection(如果你只想处理错误,你可以做左投影。 另一种选择是使用像fold[X](fa: (A) ⇒ X, fb: (B) ⇒ X): X这样的折叠myEither.fold(dealWithErrors _, happyPath _ )(虽然这意味着dealWithErrorshappyPath都返回相同的类型)