当你在尝试一门新的语言时,可能不会过于关注程序出错的问题, 但当真的去创造可用的代码时,就不能再忽视代码中的可能产生的错误和异常了。 鉴于各种各样的原因,人们往往低估了语言对错误处理支持程度的重要性。
事实会表明,Scala 能够很优雅的处理此类问题, 这一部分,我会介绍 Scala 基于 Try 的错误处理机制,以及这背后的原因。 我将使用一个在 Scala 2.10 新引入的特性,该特性向 2.9.3 兼容, 因此,请确保你的 Scala 版本不低于 2.9.3。
二.异常抛出与捕获 2.1 其他语言的错误处理机制在介绍 Scala 错误处理的惯用法之前,我们先看看其他语言(如,Java,Ruby)的错误处理机制。 和这些语言类似,Scala 也允许你抛出异常:
case class Customer(age: Int) class Cigarettes case class UnderAgeException(message: String) extends Exception(message) def buyCigarettes(customer: Customer): Cigarettes = if (customer.age < 16) throw UnderAgeException(s"Customer must be older than 16 but was ${customer.age}") else new Cigarettes被抛出的异常能够以类似 Java 中的方式被捕获,虽然是使用偏函数来指定要处理的异常类型。 此外,Scala 的 try/catch 是表达式(返回一个值),因此下面的代码会返回异常的消息:
val youngCustomer = Customer(15) try { buyCigarettes(youngCustomer) "Yo, here are your cancer sticks! Happy smokin'!" } catch { case UnderAgeException(msg) => msg } 2.2 函数式的错误处理现在,如果代码中到处是上面的异常处理代码,那它很快就会变得丑陋无比,和函数式程序设计非常不搭。 对于高并发应用来说,这也是一个很差劲的解决方式,比如, 假设需要处理在其他线程执行的 actor 所引发的异常,显然你不能用捕获异常这种处理方式, 你可能会想到其他解决方案,例如去接收一个表示错误情况的消息。
一般来说,在 Scala 中,好的做法是通过从函数里返回一个合适的值来通知人们程序出错了。 别担心,我们不会回到 C 中那种需要使用按约定进行检查的错误编码的错误处理。 相反,Scala 使用一个特定的类型来表示可能会导致异常的计算,这个类型就是 Try。
Try 的语义解释 Try 最好的方式是将它与 Option 作对比。
Option[A] 是一个可能有值也可能没值的容器, Try[A] 则表示一种计算: 这种计算在成功的情况下,返回类型为 A 的值,在出错的情况下,返回 Throwable 。 这种可以容纳错误的容器可以很轻易的在并发执行的程序之间传递。
Try 有两个子类型:
Success[A]:代表成功的计算。
封装了 Throwable 的 Failure[A]:代表出了错的计算。
如果知道一个计算可能导致错误,我们可以简单的使用 Try[A] 作为函数的返回类型。 这使得出错的可能性变得很明确,而且强制客户端以某种方式处理出错的可能。
假设,需要实现一个简单的网页爬取器:用户能够输入想爬取的网页 URL, 程序就需要去分析 URL 输入,并从中创建一个 java.net.URL :
import scala.util.Try import java.net.URL def parseURL(url: String): Try[URL] = Try(new URL(url))正如你所看到的,函数返回类型为 Try[URL]: 如果给定的 url 语法正确,这将是 Success[URL], 否则, URL 构造器会引发 MalformedURLException ,从而返回值变成 Failure[URL] 类型。
上例中,我们还用了 Try 伴生对象里的 apply 工厂方法,这个方法接受一个类型为 A 的 传名参数, 这意味着, new URL(url) 是在 Try 的 apply 方法里执行的。
apply 方法会捕获任何非致命的异常,返回一个包含相关异常的 Failure 实例。
因此, parseURL("") 会返回一个 Success[URL] ,包含了解析后的网址, 而 parseULR("garbage") 将返回一个含有 MalformedURLException 的 Failure[URL]。
三. 使用 Try 3.1 初步使用 Try使用 Try 与使用 Option 非常相似,在这里你看不到太多新的东西。
你可以调用 isSuccess 方法来检查一个 Try 是否成功,然后通过 get 方法获取它的值, 但是,这种方式的使用并不多见,因为你可以用 getOrElse 方法给 Try 提供一个默认值:
val url = parseURL(Console.readLine("URL: ")) getOrElse new URL("http://duckduckgo.com")如果用户提供的 URL 格式不正确,我们就使用 DuckDuckGo 的 URL 作为备用。
3.2 链式操作Try 最重要的特征是,它也支持高阶函数,就像 Option 一样。 在下面的示例中,你将看到,在 Try 上也进行链式操作,捕获可能发生的异常,而且代码可读性不错。
Mapping 和 Flat Mapping