引入Option优雅地保证健壮性

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

创新互联专注于企业营销型网站建设、网站重做改版、河东网站定制设计、自适应品牌网站建设、H5网站设计购物商城网站建设、集团公司官网建设、外贸营销网站建设、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为河东等各大城市提供网站开发制作服务。

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

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

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

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

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

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

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

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

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

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

 All possibilities have been elevated into the type system.
 
 
  1. def parseLocation(str: String): Option[Location] = { 
  2.  val parts = str.split(",") 
  3.  for { 
  4.    locality <- parts.optGet(0) 
  5.    theRestStr <- parts.optGet(1) 
  6.    theRest = theRestStr.split(" ") 
  7.    subdivision <- theRest.optGet(0) 
  8.    postcodeStr <- theRest.optGet(1) 
  9.    postcode <- postcodeStr.optToInt 
  10.  } yield Location(locality, subdivision, postcode)} 

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

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

optToInt方法可以如法炮制。

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

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

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

【本文为专栏作者“张逸”原创稿件,转载请联系原作者】

分享文章:引入Option优雅地保证健壮性
当前URL:http://www.hantingmc.com/qtweb/news46/485196.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联