在 Scala 中抛出异常,"official rule"是什么



我正在Coursera上学习Scala课程。我也开始读斯卡拉的《奥德斯基》一书。

我经常听到的是,在函数式语言中抛出异常不是一个好主意,因为它会破坏控制流,我们通常会返回一个"要么失败,要么成功"。Scala 2.10似乎也将提供朝着这个方向发展的Try。

但在这本书和课程中,马丁·奥德斯基似乎并没有说(至少目前)例外是不好的,他经常使用它们。我还注意到方法assert/request。。。

最后,我有点困惑,因为我想遵循最佳实践,但它们并不清楚,而且语言似乎是双向的。。。

有人能解释一下在这种情况下我应该用什么吗?

基本准则是对真正异常的**使用异常。对于"普通"故障,最好使用OptionEither。如果您与Java接口,当有人打错喷嚏时会引发异常,那么您可以使用Try来保护自己的安全。

让我们举几个例子。

假设您有一个从地图中获取内容的方法。可能出了什么问题?好吧,一些戏剧性和危险的东西,比如segfault*堆栈溢出,或者像元素这样的预期东西没有找到。您可以让segfault堆栈溢出引发异常,但如果您只是找不到元素,为什么不返回Option[V]而不是值或异常(或null)呢?

现在假设您正在编写一个程序,其中用户应该输入一个文件名。现在,如果你不只是想在出现问题时立即退出程序,那么Either就是最好的选择:

def main(args: Array[String]) {
val f = {
if (args.length < 1) Left("No filename given")
else {
val file = new File(args(0))
if (!file.exists) Left("File does not exist: "+args(0))
else Right(file)
}
}
// ...
}

现在假设您想要解析一个使用空格分隔的数字的字符串。

val numbers = "1 2 3 fish 5 6"      // Uh-oh
// numbers.split(" ").map(_.toInt)  <- will throw exception!
val tried = numbers.split(" ").map(s => Try(s.toInt))  // Caught it!
val good = tried.collect{ case Success(n) => n }

因此,你有三种方法(至少)来处理不同类型的故障:Option,因为它起作用/没有起作用,在不起作用是预期行为的情况下,而不是令人震惊和担忧的故障;Either,用于当事情可以工作或不工作时(或者,实际上,在任何情况下,如果你有两个互斥选项),并且你想保存一些关于出错的信息;和Try,当您不想自己处理异常的整个头痛问题,但仍然需要与异常愉快的代码接口时。

顺便说一句,例外是好的例子——所以我认为,你会在教科书或学习材料中比其他地方更频繁地发现它们:教科书的例子往往是不完整的,这意味着通常可以通过仔细设计来避免的严重问题应该通过抛出例外来标记。

*编辑:分段故障使JVM崩溃,无论字节码如何,都不应该发生;即使是一个例外也帮不了你。我的意思是堆栈溢出

**Edit:异常(没有堆栈跟踪)也用于Scala中的控制流——它们实际上是一种非常有效的机制,它们可以实现库定义的break语句和从方法返回的return,即使控件实际上已经传递到一个或多个闭包中。大多数情况下,您自己不应该担心这一点,除非意识到捕获所有Throwables并不是一个好主意,因为您可能会错误地捕获其中一个控制流异常

因此,这是Scala特别权衡功能纯粹性以便于从遗留语言和环境(特别是Java)转换/互操作性的地方之一。功能的纯粹性被例外打破,因为它们打破了参照的完整性,使人们无法进行平等的推理。(当然,非终止递归也有同样的作用,但很少有语言愿意强制执行那些使其不可能实现的限制。)为了保持功能的纯粹性,你可以使用Option/Mayble/Enery/Try/Validation,所有这些都将成功或失败编码为引用透明类型,并使用它们提供的各种高阶函数或底层语言特殊的monad语法来使事情变得更清楚。或者,在Scala中,你可以简单地决定放弃功能的纯粹性,因为你知道这可能会让事情在短期内变得更容易,但在长期内变得更困难。这类似于在Scala中使用"null"、可变集合或本地"var"。有点可耻,不要做太多,但每个人都在最后期限之前。

最新更新