使用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...

971
来自专栏Java技术栈

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

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

942
来自专栏我杨某人的青春满是悔恨

Swift API 设计指南(上)

本文翻译自苹果官方文档:Swift API Design Guidelines,如有错漏,欢迎指出。

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

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

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

4579
来自专栏Python爬虫实战

设计模式:工厂方法模式

工厂方法(Factory Method)模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)...

681
来自专栏工科狗和生物喵

对菜鸟教程的Python一百例的个别改进

好吧,其实是小妹子Python公选课结课,所以我来帮忙做个大作业(简单到哭的大作业好吗?)!她的大作业就是老师把菜鸟教程的Python一百例扒下来做成文档,然后...

4236
来自专栏数说工作室

正则表达式的“阿赖耶识”| 【SAS Says·扩展篇】正则表达式

阿赖耶识...为宇宙万有之本,含藏万有,使之存而不失,故称藏识。又因其能含藏生长万有之种子,故亦称种子识。 ——《佛光大辞典》 佛家说人有九识,除眼、耳、鼻、舌...

3433
来自专栏Jimoer

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

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

1423
来自专栏互扯程序

Java8函数式编程实践精华

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

1323
来自专栏更流畅、简洁的软件开发方式

面向对象的本质是什么?

  什么是面向对象的本质呢?   万物皆对象?No   抽象?No   复用?No   那到底是什么呢? 万物皆对象。问了几位网友,这是答复之一。看到了某个...

3749

扫码关注云+社区

领取腾讯云代金券