Scala API是否应该理想地抛出异常或返回Try值?是否有关于此的官方指南?
def doSomethingMayThrow(): A = ???
def doSomethingWontThrow(): Try[A] = ???
答案 0 :(得分:12)
永远不要为可恢复的错误抛出异常。
返回表示可能失败的适当数据结构(Future
,Try
,Either
等等)总是优于在野外抛出异常。它将告知呼叫者失败的可能性,并将迫使他们进行管理。
只应针对不可恢复的错误抛出异常,例如硬件故障和类似错误。
我们举个例子:
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