引入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来访问。

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2017-01-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

【干货】Python自动化审计及实现

本文由马哥教育Python自动化实战班导师wayne推荐,作者为SSS团队,转载自互联网,内容略经小编改编和加工,观点跟作者无关,最后感谢作者的辛苦贡献与付出。...

32410
来自专栏喵了个咪的博客空间

phalcon-入门篇8(Model层基础使用2)

phalcon-入门篇8(Model层基础使用2) ? 本教程基于phalcon2.0.9版本 先在这里感谢各位phalcon技术爱好者,我们提供这样一个优秀的...

3634
来自专栏AI科技大本营的专栏

10分钟快速入门Python函数式编程

本文,你会了解到什么是函数式编程,以及如何用 Python 进行函数式编程。你还会了解到列表解析和其他形式的解析。

602
来自专栏深度学习自然语言处理

【干货】python正则表达式应用笔记

正则表达式 (Regular Expression) 又称 RegEx, 是用来匹配字符的一种工具. 在一大串字符中寻找你需要的内容. 它常被用在很多方...

3058
来自专栏杨建荣的学习笔记

Python代码重构参考

在数据库方向上相对来说能够容易推出开发规范和标准,但是你很少听到公司里面出针对开发同学的开发规范。我觉得其中的一个原因是程序猿比较执拗,以技术服人,经常...

1030
来自专栏编程

自学Python笔记(二)

作为最最基础的初学者,尤其是面对中小学生学习Python我想大概了解一下Python,能编个小程序,能看懂一般的程序就可以,如果想深一步的学习还是需要静下心来好...

1627
来自专栏aCloudDeveloper

指向函数的指针

Author: bakari   Date: 2012.8.8 做好总结我觉得是把知识学扎实必不可少的实践环节。这个知识点是当初自己在学习这一块做的一些笔记,现...

1796
来自专栏数说戏聊

Python3分析Excel数据

使用xlrd和xlwt扩展包,确定工作簿中工作表的数量、名称和每个工作表中行列的数量。 1excel_introspect_workbook.py

522
来自专栏张善友的专栏

ASP.NET开发人员需要学习ASP.NET MVC么?

InfoQ上有篇文章ASP.NET开发人员需要学习ASP.NET MVC么? 文章已经覆盖了绝大多数对于学习/使用ASP.NET MVC的观点。 我觉得ASP....

1789
来自专栏数据小魔方

左手用R右手Python系列13——字符串处理与正则表达式

学习数据分析,掌握一些灵巧的分析工具可以使得数据清洗效率事半功倍,比如在处理非结构化的文本数据时,如果能够了解一下简单的正则表达式,那么你可以免去大量的冗余代码...

3374

扫码关注云+社区