引入Option优雅地保证健壮性

简介: 引入Option优雅地保证健壮性

REA的Ken Scambler在其演讲2 Year of Real World FP at REA》中,总结了选择函数式编程的三个原因:Modularity, Abstraction和Composability。

函数式编程强调纯函数(Pure Function),这是模块化的一个重要基础,因为对于纯函数而言,可以不用考虑调用的上下文,就可以根据函数的输入推断函数的执行结果。这也就是Ken所谓的:

You can tell what it does without Looking at surrounding context.

Ken在演讲中给出了一个案例:

def parseLocation(str: String): Location = {
  val parts = str.split(",")
  val secondStr = parts(1)
  val parts2 = secondStr.split(" ")
  Location(parts(0), parts2(0), parts(1).toInt)}

仔细阅读这段代码,你会发现这段代码是不健壮的,可能存在如下错误:

  • 作为input的str可能为null
  • parts(0)和parts(1)可能导致索引越界
  • parts2(0)可能导致索引越界
  • parts(1)未必是整数,调用toInt可能导致类型转换异常

这段代码隐含的错误还可能被广泛地蔓延到系统的其他地方,只要该函数被调用。这种蔓延可能会因为更多嵌套的调用而产生级联的错误效应。例如:

def doSomethingElse(): Unit = {
  // ...Do other stuff
  parseLocation("Melbourne, VIC 3000")}

doSomethingElse()函数又被其他函数调用,这些潜在的缺陷会分布到各个直接或间接的调用点。这意味着代码会继承它所调用代码的错误以及副作用,使得对代码功能的推理(reasoning)变得近乎不可能,更不用说代码的模块化(modularity)了。

我们当然可以通过对null进行检测来避免出现这些错误。然而看看各种出现null值的可能分支,需要我们做各种条件判断,想象这样的代码都让人不寒而栗。引入Option类型就可以很好地封装这种可能性。按照Ken的说法就是:

All possibilities have been elevated into the type system.

def parseLocation(str: String): Option[Location] = {
val parts = str.split(",")
for {
   locality <- parts.optGet(0)
   theRestStr <- parts.optGet(1)
   theRest = theRestStr.split(" ")
   subdivision <- theRest.optGet(0)
   postcodeStr <- theRest.optGet(1)
   postcode <- postcodeStr.optToInt
} yield Location(locality, subdivision, postcode)}

以上代码中,split()函数返回的类型为Array[String],该类型自身是没有optGet()函数的。但是我们可以为Array[String]定义隐式转换:

implicit class StringArrayWrapper(array: Array[String]) {
    def optGet(index:Int): Option[String] = {
        if (array.length > index) Some(array(index)) else None
    }}

optToInt方法可以如法炮制。

Ken的解决方案并没有考虑到parseLocation函数入参str存在null值的可能,故而在对str调用split方法时仍然有可能导致抛出空指针异常。因此进一步,我们还可以修改parseLocation函数的定义:

def parseLocation(optStr: Option[String]): Option[Location]

显然,通过引入Option,既规避了前面分析可能出现的错误,又能避免编写繁琐的if判断。这里的关键点是Option对两种可能性(None与Some)的封装。它由两个代数类型Some与None构成,前者包含了一个值,而后者则包含了一个不存在的值。事实上,Option是一个Maybe Monad,实现了flatMap与filter,因而在Scala中可以用for comprehension来访问。

相关文章
|
算法 数据可视化 vr&ar
【图形学】探秘图形学奥秘:区域填充的解密与实战
【图形学】探秘图形学奥秘:区域填充的解密与实战
190 0
|
消息中间件 存储 SQL
如何保障消息中间件 100% 消息投递成功?如何保证消息幂等性?
一、前言 我们小伙伴应该都听说够消息中间件MQ,如:RabbitMQ,RocketMQ,Kafka等。引入中间件的好处可以起到抗高并发,削峰,业务解耦的作用。
334 61
如何保障消息中间件 100% 消息投递成功?如何保证消息幂等性?
|
NoSQL Linux Redis
Linux Centos7 下使用yum安装Redis
如何在Linux Centos7 下使用yum安装redis
1248 0
|
数据采集 XML 数据可视化
使用Objective-C和ASIHTTPRequest库进行Douban电影分析
Douban是一个提供图书、音乐、电影等文化内容的社交网站,它的电影频道包含了大量的电影信息和用户评价。本文将介绍如何使用Objective-C语言和ASIHTTPRequest库进行Douban电影分析,包括如何获取电影数据、如何解析JSON格式的数据、如何使用代理IP技术和多线程技术提高爬虫效率,以及如何对电影数据进行简单的统计和可视化。本文将为您提供一种详细的方法,以便在Objective-C环境下进行网络爬虫和数据处理。
208 0
使用Objective-C和ASIHTTPRequest库进行Douban电影分析
|
JavaScript 安全
原生JS路由,iframe框架
原生JS路由,iframe框架
|
Linux Android开发 开发者
Android后台杀死系列之二:ActivityManagerService与App现场恢复机制
Android后台杀死系列之二:ActivityManagerService与App现场恢复机制
683 0
Android后台杀死系列之二:ActivityManagerService与App现场恢复机制
|
开发框架 网络协议 Java
JAVA菜鸟成长记——JNDI
JAVA菜鸟成长记——JNDI
263 0
JAVA菜鸟成长记——JNDI
|
监控 Cloud Native 前端开发
基于云原生网关的可观测性最佳实践
本文主要介绍了基于云原生网关构造可观测性能力的最佳实践,并通过介绍的三种实践覆盖了白盒观测,黑盒观测,基于网关构造业务可观测性等方面,针对可观测,虽然目前用户可以基于目前的可观测体系来快速发现,定位问题, 但我们目前能做的还有很多。
基于云原生网关的可观测性最佳实践
|
监控 容器 Perl
Kubernetes日志采集Sidecar模式介绍
DaemonSet和Sidecar模式各有优缺点,目前没有哪种方式可以适用于所有场景。因此我们阿里云日志服务同时支持了DaemonSet以及Sidecar两种方式,并对每种方式进行了一些额外的改进,更加适用于K8S下的动态场景。
20982 0
Kubernetes日志采集Sidecar模式介绍
|
网络协议 Shell PHP
原创 今日学习
原创 今日学习
187 0
原创 今日学习