使用Option的正确姿势

我们会频繁地使用Scala的Option,用以解决类似Null Object之类的问题。某种程度讲,使用Option必然会减少对空指针引用判断的丑陋代码,结合For Comprehension,确乎是Scala编程中的一把利器。我在博客《引入Option优雅地保证健壮性》与《并非Null Object这么简单》中都详细对Option的本质与运用进行剖析与介绍。

然而,Option虽然好,我们却不可“贪杯”哦!

从语义上讲,Option代表一种容器(Monad)非空即有的两种状态,例如List的headOption就是对Option的合理诠释。那么,是否只要是两种状态的业务场景,就可以使用Option呢?例如,将函数的参数类型定义为Option类型,用以表示用户传参的选择:传入实际值或者不传值。这是否是得体的姿势?

Daniel Westheide发表的博文When Option Is Not Good Enough旗帜鲜明地表达了反对意见。他给出这样的一个案例:根据产品标题与零售商信息查询Offer:

def searchOffers(
  productTitle: Option[String],
  retailer: Option[Retailer]
  ): Seq[Offer] = ???

作为这个函数的调用者,我们该怎么看待这两个Option参数传递的业务含义?如果productTitle为None,是表示忽略productTitle的值,仅仅搜索符合retailer条件的offers;还是搜索没有提供productTitle的Offer记录?同样,retailer参数也传递了如此模糊不清的意图!

好的代码尤其是接口应该是”不言自明“清晰地传递开发者意图。落到具体的业务场景,则代码就应该恰到好处干净利落地表现其业务含义。接口体现准确的业务通用语言(ubiquitous language),是DDD的核心价值。

如果我们为这两个搜索条件定义表达业务含义的代数数据类型(algebraic data types),如下代码所示,表意无疑要清晰许多:

sealed trait SearchCriteriaobject SearchCriteria {
  final case object MatchAll extends SearchCriteria
  final case class Contains(s: String) extends SearchCriteria
}
sealed trait RetailerCriteriaobject RetailerCriteria {
  final case object AnyRetailer extends RetailerCriteria
  final case class Only(retailer: Retailer) extends RetailerCriteria
}

def searchOffers(
  product: SearchCriteria,
  retailer: RetailerCriteria
  ): Seq[Offer] = ???

SearchCriteria与RetailerCriteria作为两个查询条件,分别提供了各自的查询语义,显然要比过分抽象的Some与None更加清晰可读。

引入这样的代数数据类型不仅可以让代码的表意更清晰,还可更好地应对需求的变化。对于现有的SearchCriteria定义而言,倘若要牵强附会,确实可以强词夺理地说:MatchAll就是None的语义,而Contains则对应着Some。然而,如果需求要求增加完全匹配的查询场景,对于Option类型而言,该如何表达?回到SearchCriteria的定义,我们可以轻松地为其增加一种类型:

object SearchCriteria {
  final case object MatchAll extends SearchCriteria
  final case class Contains(s: String) extends SearchCriteria
  final case class Exactly(s: String) extends SearchCriteria
}

比较Option而言,增加了一种新的类型,却极大地提高了代码的可读性,也为代码的未来扩展奠定了基础。与获得的收益相比,仅仅是付出新增类型的微末代价,何足道哉!

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

原文发表时间:2017-02-23

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏守候书阁

重构 - 用各种方式优化自己的函数库

最近有几天时间空闲,也是在学怎么写更有可读性的代码,更简单,方便的API。简单来说就是重构方面的内容。今天简单分享下,对以前一个小项目(ecDo,欢迎大家sta...

911
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day08-超市购物系统总结

Java基础-day08-超市购物系统总结 超市购物小票——自定义类 1案例介绍与演示 将超市购物小票案例中零散数据(名称、货号、单价、计价单位等)封装为货物对...

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

对一道if-else相关的程序题的简单分析(r5笔记第45天)

今天同事在微信群里提出了一个问题,看似是一个面试题。 大体的意思就是补充下面if条件使得输出 Hello World if(){ System.out.prin...

3555
来自专栏C语言及其他语言

第一个 C 语言编译器是怎样编写的?

作者: 伯乐在线 - Chaobs 网址: http://blog.jobbole.com/94311/ 首先向C语言之父Dennis Ritchie致敬! ...

3739
来自专栏顶级程序员

Python 工匠:善用变量来改善代码质量

我一直觉得编程某种意义上是一门『手艺』,因为优雅而高效的代码,就如同完美的手工艺品一样让人赏心悦目。

1123
来自专栏PHP在线

5个经典的JavaScript面试题

在IT界中公司对JavaScript开发者的要求还是比较高的,但是如果JavaScript开 发者的技能和经验都达到了一定的级别,那他们还是很容易跳到优秀的公司...

3468
来自专栏Golang语言社区

第十节 Go语言函数方法(上)

干货来了!!!为了让更多的小伙伴喜欢Golang、加入Golang之中来,Golang语言社区发起人彬哥联合业界大牛共同推出了Go语言基础、进阶、提高课程,目前...

702
来自专栏Jimoer

Java设计模式学习记录-建造者模式

今天周末,有小雨,正好也不用出门了,那就在家学习吧,经过了两周的面试,拿到了几个offer,但是都不是自己很想去的那种,要么就是几个人的初创小公司,要么就是开发...

1343
来自专栏Java技术栈

跟我学 Java 8 新特性之 Stream 流(五)映射

经过了前面四篇文章的学习,相信大家对Stream流已经是相当的熟悉了,同时也掌握了一些高级功能了,如果你之前有阅读过集合框架的基石 Collection 接口,...

842
来自专栏互扯程序

Java8函数式编程实践精华

现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

1113

扫码关注云+社区