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

相关文章
|
7月前
|
存储 Linux 调度
确保并发执行的安全性:探索多线程和锁机制以构建可靠的程序
在当今计算机系统中,多线程编程已成为常见的需求,然而,同时也带来了并发执行的挑战。为了避免数据竞争和其他并发问题,正确使用适当的锁机制是至关重要的。通过阅读本文,读者将了解到多线程和锁机制在并发编程中的重要性,以及如何避免常见的并发问题,确保程序的安全性和可靠性。通过实际案例和代码示例来说明如何正确地使用多线程和锁机制来构建可靠的程序。
20 1
|
存储 缓存 算法
ES写入过程和写入原理调优及如何保证数据的写一致性(上)
ES写入过程和写入原理调优及如何保证数据的写一致性
ES写入过程和写入原理调优及如何保证数据的写一致性(上)
|
25天前
|
存储 负载均衡 并行计算
实现优雅并行编程:确保正确性与提升性能的关键要素
在程序开发中,并行编程一种利用多个处理器或计算资源同时执行多个任务的编程方式,它能够提高计算效率和性能,是提高计算效率和性能的关键手段,但它也带来了一系列复杂的问题,涉及到任务分解、数据同步、资源分配等诸多复杂问题,稍有不慎就可能导致性能瓶颈、死锁甚至数据不一致等状况。编写优雅的并行程序需要在保证程序正确性的前提下,实现高效的并行计算。那么本文就来探讨一下如何在保证程序正确性的前提下,实现优雅的并行程序,以提升计算效率和性能,包括任务分解、数据同步和资源分配等方面的关键要素,希望能够为读者提供一些有用的指导和启示。
17 2
实现优雅并行编程:确保正确性与提升性能的关键要素
|
4月前
|
监控 数据可视化 测试技术
什么是非功能性测试?
什么是非功能性测试?
|
10月前
|
存储 机器人 应用服务中间件
|
12月前
|
分布式数据库 数据库
复制延迟案例(1)-最终一致性
该案例违反因果律。 想象先生和夫人之间的对话: Mr Mrs,你能看到多远未来? Mrs 通常约10s,Mr.
68 0
|
12月前
|
存储 移动开发 运维
无主复制系统(3)-Quorum一致性的局限性
若有n个副本,且配置w和r,使得w + r > n w + r> nw+r>n,期望可以读到一个最新值。因为成功写入的节点集合和读取的节点集合必有重合,这样读取的节点中至少有一个具有最新值,如图-11。
97 0
|
测试技术 数据库
【可靠性测试】什么是可靠性测试:定义、方法和工具
可靠性定义为在特定环境中指定时间段内无故障软件运行的概率。 执行可靠性测试是为了确保软件是可靠的,它满足其目的,在给定的环境中指定的时间量,并能够呈现无故障运行。
|
缓存 安全 Java
遵循Happens-Before规则来保证可见性|而非掌握所有底层
基于JSR -133内存模型提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。要保证可见性,就是遵守 Happens-Before 规则,合理的使用java提供的工具。
116 0
|
XML NoSQL Redis
分布式服务器框架之Server.Common封装CSRedisCore实现RedisDBClient 双重检验锁检验初始化CSRedisClient单例
自己封装的RedisDBClient代码量很少,基本原理就是在CSRedisCore的基础上封装了一层,使用xml配置里的RedisConnectString去New了一个CSRedisClient,然后这个Redis客户端交给了RedisHelper.Initialization函数去初始化。