使用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 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

通过SmtpClient发送Exchange会议邮件

看到C#中调用Outlook API 发起会议 ,这个完全可以用SMTP方式实现的,下面我的项目中使用的代码: 对于.NET而言,从2.0开始,发邮件已经是一件...

1949
来自专栏王磊的博客

MySQL数据库工具类之——DataTable批量加入MySQL数据库(Net版)

MySQL数据库工具类之——DataTable批量加入数据库(Net版),MySqlDbHelper通用类希望能对大家有用,代码如下: using MySql....

3649
来自专栏菩提树下的杨过

基于sliverlight + wcf的web 文字版IM 示例

演示地址: http://task.24city.com/default.html 预览界面: ? 一、布局 采用Grid布局,5行2列 第一行:为登录/注册信...

3226
来自专栏张善友的专栏

弹出式模态窗体选择文本控件

2006年就要到来了,最近比较忙,很少更新blog,今天发一个模态窗体选择文本控件辞旧迎新.新年在发几个asp.net2.0 webPart控件同各位分享: ...

1907
来自专栏c#开发者

about store RecordField submit emptystring issue

operate screenshot When click save button submit to change,trace store before...

3447
来自专栏xingoo, 一个梦想做发明家的程序员

【插件开发】—— 6 SWT 复杂控件使用以及布局

前文回顾: 1 插件学习篇 2 简单的建立插件工程以及模型文件分析 3 利用扩展点,开发透视图 4 SWT编程须知 5 SWT简单控件的使用与布局搭...

2349
来自专栏木宛城主

曾今的代码系列——自己的分页控件+存储过程实现分页

项目里面的测试代码,仅供参考 LoginByAjax <title>Ajax登陆</title> <script src="Scripts/c...

1865
来自专栏james大数据架构

CSS好看的按钮

好看的按钮 <style> .btn { BORDER-RIGHT: #7b9ebd 1px solid; PADDING-RIGHT: 2px; BORDE...

2017
来自专栏我和未来有约会

Silverlight制作逐帧动画 v2 - part2

Silverlight制作逐帧动画 v2 - part2 在这里完善了一下算法,加入了fps的机制进去。 private string[] ...

1896
来自专栏田超学前端

【微信小程序】c# 实现获取openid、session_key 服务端

5280

扫码关注云+社区