为什么在Scala中优先选择错误处理选项?

时间:2014-07-06 11:36:47

标签: scala exception-handling functional-programming

所以我正在学习Scala的功能,并且书中说异常会破坏引用透明度,因此应该使用Option,如下所示:

def pattern(s: String): Option[Pattern] = {
  try {
    Some(Pattern.compile(s))
  } catch {
    case e: PatternSyntaxException => None
  }
}

这看起来很糟糕;我的意思是它似乎相当于:

catch(Exception e){
    return null;
}

除了我们可以区分“null for error”和“null as genuine value”这一事实。它似乎应该至少返回包含错误信息的内容,如:

catch {
    case e: Exception => Fail(e)
}

我错过了什么?

5 个答案:

答案 0 :(得分:11)

在此特定部分,Option主要用作示例,因为使用的操作(计算mean)是部分函数,​​它不会为所有可能的值生成值(集合可能是空的,因此没有办法计算平均值)Option可能是一个有效的案例。如果由于集合为空而无法计算mean,则只需返回None

但是还有很多其他方法可以解决这个问题,你可以使用Either[L,R]Left是错误结果而Right是好结果,你仍然可以抛出异常并将其包装在Try对象中(由于它在PromiseFuture计算中使用,现在看起来更常见),如果出现错误,可以使用ScalaZ Validation实际上是一个验证问题。

您应该从这一部分开始的主要概念是错误应该是函数的返回类型的一部分,而不是某些不能由类型合理声明的魔术操作(异常)。

作为一个无耻的插件,我做了关于Either and Try here的博客。

答案 1 :(得分:8)

如果您没有问“为什么Option比例外更好?”这个问题会更容易回答?并且“为什么Option优于null?”并且“为什么OptionTry更好?”一切都在同一时间。

第一个问题的答案是,在非真正特殊情况下使用异常会使程序的控制流程变得混乱。这就是引用透明度的地方 - 如果我可以根据值思考并且不必跟踪抛出和捕获异常的位置,那么我(或您)就可以更容易地推理您的代码。

第二个问题的答案(为什么不为null?)就像“你曾经在Java中处理NullPointerException吗?”。

对于第三个问题,一般来说你是对的 - 最好使用类似Either[Throwable, A]Try[A]的类型来表示可能失败的计算,因为它们允许您传递更详细的信息关于失败。但是,在某些情况下,当一个函数只能以一种明显的方式失败时,使用Option是有意义的。例如,如果我在地图中执行查找,我可能并不真正需要或想要像Either[NoSuchElementException, A]之类的东西,其中错误是如此抽象,以至于我可能最终将其包含在更多内容中特定领域。因此,地图上的get只会返回Option[A]

答案 2 :(得分:3)

您应该使用util.Try

scala> import java.util.regex.Pattern
import java.util.regex.Pattern

scala> def pattern(s: String): util.Try[Pattern] = util.Try(Pattern.compile(s))
pattern: (s: String)scala.util.Try[java.util.regex.Pattern]


scala> pattern("<?++")
res0: scala.util.Try[java.util.regex.Pattern] =
Failure(java.util.regex.PatternSyntaxException: Dangling meta character '+' near index 3
<?++
   ^)

scala> pattern("[.*]")
res1: scala.util.Try[java.util.regex.Pattern] = Success([.*])

答案 3 :(得分:2)

天真的例子

def pattern(s: String): Pattern = {
  Pattern.compile(s)
}

有副作用,它可以通过除结果之外的其他方式影响使用它的程序(它可能导致异常)。这在函数式编程中是不受欢迎的,因为它增加了代码的复杂性 代码

def pattern(s: String): Option[Pattern] = {
  try {
    Some(Pattern.compile(s))
  } catch {
    case e: PatternSyntaxException => None
  }
}

封装产生程序部分的副作用。模式失败的原因信息丢失了,但有时只关注它是否失败。如果方法失败的原因很重要,可以使用Try(http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.util.Try):

def pattern(s: String): Try[Pattern] = {
   Try(Pattern.compile(s))
}

答案 4 :(得分:0)

我认为其他两个答案会为您提供有关如何继续的好建议。我仍然认为在Scala的类型系统中使用底部类型Nothing很好地表示抛出异常。所以这是一个很好的类型,我不会完全称它为“魔术操作”。

但是......如果您的方法通常会导致无效值,那么如果您的调用方非常合理地希望立即处理这样的无效值,那么使用OptionEitherTry是一个很好的方法。在一个场景中,您的呼叫站点并不真正知道如何处理这样的无效值,特别是如果它是例外条件而不是常见情况,那么您应该使用例外IMO。 / p>

异常的问题恰恰不在于它们不能很好地处理函数式编程,而是当它们有副作用时很难推理它们。因为那么您的呼叫站点必须确保在异常情况下撤消副作用。如果您的呼叫站点纯粹是功能性的,则传递异常不会造成任何损害。

如果任何使用整数执行任何操作的函数会因为被零除或溢出的可能性而声明其返回类型为Try,这可能会使代码完全混乱。使用异常的另一个非常好的理由是无效的参数范围或要求。如果您希望参数是0x之间的整数,如果它不符合该属性,您可能会抛出IllegalArgumentException;在斯卡拉方便地:require(a >= 0 && a < x)