Scala函数式编程(五) 函数式的错误处理

前情提要

Scala函数式编程指南(一) 函数式思想介绍

scala函数式编程(二) scala基础语法介绍

Scala函数式编程(三) scala集合和函数

Scala函数式编程(四)函数式的数据结构 上

Scala函数式编程(四)函数式的数据结构 下

1.面向对象的错误处理

在介绍scala的函数式的错误处理之前,我们要先来介绍一下其他情况下的错误处理方式。

以java为例,常见的错误处理方式不外乎两种,一种是及时捕捉到异常,然后当场进行处理。

try{
    ... 
}catch(Exception e){
    ...
    
}finally{
    
}

另一种则是将异常抛出,层层捕获,然后在最上层对异常进行统一处理,这种通常是在大型项目的时候会使用。

这两种错误处理的方法是,在我们日常的编程中,已经足以应对多种情况。

但在函数式编程中却不行,函数式编程追求的是无副作用的代码,无副作用最直接的应用就是可以放心得并发运行,而抛出异常却会产生副作用。

try catch处理的弊端,在并发编程中其实有较为明显的体现。

以spark为例,如果spark主节点master询问worker节点的健康情况,当worker节点出现异常时,显然让master节点来捕获并处理这个异常,有点不符合情理。

更合理的处理,应该是让master接收到一个表示错误情况的消息,然后再决定接下来如何处理。而worker的异常就让worker自己去解决吧。

而在scala中,有一种特定的类型,它用来表示可能导致异常的一个计算过程,这就是Try。

2.从Option到Try

前面有介绍过Option,相关介绍可以看这里Scala函数式编程(三) scala集合和函数

这里简单介绍一下Option。

Option呢,其实就是薛定谔的值,里面可能有值,也可能没有值。只有到要看的时候,才会知道Option里面到底有没有值。

Option全程叫Option[A],表示Option里面存的是A类型的值,这个A可以是Int,String,等等。我们可以通过get这个api来获取Option[A]里面的值,当不存在时,get会返回None。

可以通过isEmpty,来确认Option里面到底是不是有值。也可以通过getOrElse来指定没有值的时候要返回什么值。

Try[A]和Option类似,都是表示一个可能有也可能没有的东西。实际对应过来, Try[A]就表示一个可能成功也可以失败的计算,如果成功,则返回A类型,如果失败,则返回Throwable。

先最在交互式环境中直观看一下怎么使用吧:

scala> import scala.util.Try
import scala.util.Try

scala> Try(1+1)
res15: scala.util.Try[Int] = Success(2)

scala> Try(1/0)
res16: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)

能够实现这个功能,主要是因为Try的两个子类型:

  • Success[A]:代表成功的计算。
  • 封装了 Throwable 的 Failure[A]:代表出了错的计算。

是不是和Option很像呢?也是薛定谔的错误,在没打开来看之前,Try里面可能是成功的,也可能是失败的。

同样可以通过isSuccess和isFailure来确认到底这个Try是成功还是失败。

如果一个函数中有一个计算可能会出错,那么我们就可以直让函数返回Try,然后对成功还是错误,就全交由调用者来进行处理,比如上面说到的,Spark的那个例子。

3.Try的使用

上面初步介绍了Try的含义和用法,接下来就来看看Try这个东西,还有哪些常规的用法吧。

3.1 map

map是scala里面非常常用的一种操作,Try里面也有!

对Try使用Map的话,会将一个是Success[A]的Try[A]映射到Try[B]会得到Success[B]。如果它是Failure[A],就会得到Failure[B],而且包含的异常和Failure[A]一样。

看看例子吧:

//新建一个Try,注意,这里是Try[Int]
scala> val tryMap = Try(1+1)
tryMap: scala.util.Try[Int] = Success(2)

//使用Map,让它变成Try[String]了
scala> tryMap.map(_.toString)
res46: scala.util.Try[String] = Success(2)

//新建一个会失败的Try[Int]
scala> val tryMapFail = Try(1 / 0)
tryMapFail: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)

//转换成Try[String]了,但Failure的异常类型不变
scala> tryMapFail.map(_.toString)
res47: scala.util.Try[String] = Failure(java.lang.ArithmeticException: / by zero)

Try不止支持map,还支持for,flatMap,filter等常规操作,从这个角度看,Try反而更像一种数据结构。

3.2 错误时候的默认值getOrElse

和Option一样,Try还很方便得提供了getOrElse这个方法。当你想为失败的时候做些什么的时候就可以用这个api。

这个我举个简单的例子,将字符串转换为Int类型。在字符串转Int类型的时候呢,可能会遇到一些不符合规范的数据。这时候你就不得不考虑数据是否可以安全得转换成Int,但有了Try,可以很方便得用getOrElse,方法。

当遇到不能转成Int的字符串,给与一个默认值即可。

scala> import scala.util.Try
import scala.util.Try

scala> "12".toInt
res17: Int = 12

scala> "asd".toInt
java.lang.NumberFormatException: For input string: "asd"
  at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  at java.lang.Integer.parseInt(Integer.java:580)
  at java.lang.Integer.parseInt(Integer.java:615)
  at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:272)
  at scala.collection.immutable.StringOps.toInt(StringOps.scala:29)
  ... 32 elided

scala> Try("asd".toInt).getOrElse(-1)
res19: Int = -1

但这里还是得多说一句,这种做法会忽略掉原本应该抛出的错误,你需要明确知道自己确实是要忽略掉这个错误才能这样用

否则可能因为设置的默认值导致出现问题,而毫无头绪,因为程序并没有报任何错误!!

3.3 模式匹配

我们可以不必如java的try catch那般去处理Try失败时返回的异常。因为我们有scala的模式匹配。

不得不说,模式匹配真的是很强大的一个语言特性。前面不是说到嘛,Try有两个子类,Success和Failure,成功时候返回Success,失败时返回Failure。

所以我们就能够这样做:

import scala.util.Success
import scala.util.Failure
val operation = Try(1 / 0)
operation match {
  case Success(num) => println(num)
  case Failure(ex) => println(s"Problem is ${ex.getMessage}")
}

因为除数为0,所以这个Try是失败的,所以这里会输出:Problem is / by zero

scala强大的模式匹配,可以方便得让我们处理错误和非错误的情况。

4. 小结

Scala 的错误处理和其他范式的编程语言有很大的不同。 Try 类型可以让你将可能会出错的计算封装在一个容器里,并优雅的去处理计算得到的值。 并且可以像操作集合和 Option 那样统一的去操作 Try。

同时Try[A]也支持常见数据结构中的操作,诸如Map,Filter等常规的api都支持。

Try这种错误处理的方式,明显更适用于函数式的情况,也就是说更适合在并发编程的时候使用。

但在我看来,Try也是有一些不好的地方,比如说在代码可读性方面就比try catch这种方式差。不得不说,虽然写起来比较啰嗦,但看着这个结构确实是一目了然。

但是不管如何,在我看来,函数式的错误处理依旧是很有趣的一个东西。如果合适的话,可以多在代码中尝试去使用:)

以上~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容