Scala:投掷错误与返回尝试?

时间:2014-08-31 15:38:38

标签: scala

Scala API是否应该理想地抛出异常或返回Try值?是否有关于此的官方指南?

def doSomethingMayThrow(): A = ???
def doSomethingWontThrow(): Try[A] = ???

2 个答案:

答案 0 :(得分:12)

永远不要为可恢复的错误抛出异常。

返回表示可能失败的适当数据结构(FutureTryEither等等)总是优于在野外抛出异常。它将告知呼叫者失败的可能性,并将迫使他们进行管理。

只应针对不可恢复的错误抛出异常,例如硬件故障和类似错误。

我们举个例子:

def throwOnOdds(x: Int): Int =
   if (x % 2 == 0) x / 2 else throw new Exception("foo")

val x = throwOnOdds(41) + 2 // compiles and explodes at runtime

现在,让我们做得更好

def throwOnOdds(x: Int): Try[Int] =
   if (x % 2 == 0) Success(x / 2) else Failure(new Exception("foo"))

val x = throwOnOdds(41) + 2 // doesn't compile

不处理失败会导致编译时错误,这比运行时错误要好。这是如何处理它

throwOnOdds(41) match {
  case Success(n) => // do something with n
  case Failure(e) => // handle exception
}

答案 1 :(得分:2)

请参见monadic数据类型。使用monadic数据类型比抛出异常更具表现力和清晰度,并且是声明性地处理所有情况而没有不明副作用的首选方式。 http://en.wikipedia.org/wiki/Monad_(functional_programming)

使用失败与成功并使用map和flatMap表达“快乐路径”的优势。是异常/失败在链中变得明确。

如果您调用doSomethingMayThrow可能会产生副作用(例如抛出异常),那么在使用monadic数据类型时非常清楚。

这将有助于查看现实世界的案例。我将使用它,因为它是我最近工作的东西:

考虑缓存场景中的monadic future - 如果缓存返回结果,则可以处理结果。如果缓存没有返回结果,那么我们可以转到我们尝试缓存结果的实际服务,并且我们可以非常明确地表达它而没有任何不明确的隐含副作用,例如异常:

这里recoverWith就像错误情况下的flatMap(返回Future [Model]而不是失败)。 recover就像错误情况下的map(返回未来的模型而不是失败)。然后map会获取所得到的模型并对其进行处理 - 所有案例都在代码中明确定义,因此单个表达式中的所有案例和条件都清晰明了。

(userCacheActor ? GetUserModel(id="abc"))
    .recoverWith(userServiceActor ? GetUserModel(id="abc"))
    .recover(new ErrorModel(errorCode=100, body="service error")
    .map(x: Response => createHttpResponseFromModel(x))

def createHttpResponseFromModel(x: Model) => 
    x match {
        case model: ErrorModel => ??? 
        case model: UserModel => ???
    } 

同样,所有内容都有明确的标记 - 如何处理缓存命中失败,如果服务无法在该场景中做出响应该怎么办,如何处理结果模型的结尾无论如何处理。

通常将flatMap称为“水管工”。 monad或者'管道工'快乐的道路。 flatMap允许您带另一个Monad并将其返回。所以你可以试试'多个场景并编写快乐路径代码,最后收集任何错误。

scala> Option("Something").flatMap(x => Option( x + " SomethingElse"))
    .flatMap(x => None).getOrElse("encountered None somewhere")
res1: String = encountered None somewhere


scala> scala.util.Try("tried")
    .flatMap(x => scala.util.Try( x + " triedSomethingElse"))
    .flatMap(x => scala.util.Try{throw new Exception("Oops")})
    .getOrElse("encountered exception")
res2: String = encountered exception