我注意到许多开源Scala API使用Future
(如Slick)或Java风格的异常处理(如spray-json)而不是Try
或Either
。
在没有长时间运行或异步的简单开源API中返回Try
或Either
是否存在问题?我应该避免这些,如果是的话,我应该使用什么呢?
(显然,我更倾向于使用核心Scala API而不是第三方库,例如Scalaz,以避免迫使下游用户也使用这些库。)
答案 0 :(得分:4)
Try
适用于非异步操作的包装异常
但是,我认为使用异常实例返回非异常错误条件不是正确。当API是异步的并使用Future
时,我也是这样。
将异常错误与业务错误混合在一起很容易导致API客户端(也就是我:)的错误处理不正确/不足。
如果您决定仍使用例外来表示您的业务错误,请记住异常带有隐藏价格:构建堆栈跟踪。如果您创建自己的业务例外并混合scala.util.control.NoStackTrace
,则可以减轻这种情况。
Scala标准库中的默认Either
实现是无偏的,这意味着区分成功和失败的情况仅依赖于约定:
右是正确的(正确),因此左错(错误)。
不偏不倚的一面也让人很难跟上"快乐"路径(或使错误恢复脱颖而出)
此时您的最佳选择(scala 2.11.6和相应的标准库)将根据您的依赖关系和您希望向用户公开的API而有所不同,但它不会来自scala标准库。
Validation
或\/
,则可以选择Or
type或Accord modes
可能是一个选项,但我不太了解它与其他人进行比较。您可能已经依赖于提供自己的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 _ )
(虽然这意味着dealWithErrors
和happyPath
都返回相同的类型)