查看原文
其他

左闭右开,这是最基本的代码准则。

李鹏 分布式实验室 2022-12-29

区间或范围在编程世界中无处不在。典型的例子是选择开始和结束日期,就像你在Airbnb上预订房间或预订航班时那样。这样的例子有很多:从切片JS数组,到Java的List#sublist甚至SQL的LIMIT运算符,区间无处不在。

你有没有想过为什么它们的设置总是[closed, open)而不是[closed, closed]?


 1 
什么是 [closed, open) 区间?


一个闭-开区间,通常表示为[a, b),这是一个缩写形式,用于表达a <= x < b的所有值的集合,从a开始并包括a,到b但不包括b,例如,如果我们使用整数,那么[0, 5) == 0, 1, 2, 3, 4。

一个闭-闭区间,通常表示为[a, b],是包含最后一个值的区间,例如[0, 5] == 0, 1, 2, 3, 4, 5。


 2 
永远不要使用[closed, closed]区间


几年前,我有幸在一个广泛使用[closed, closed]区间的系统中工作。该系统在大多数情况下能良好运行,但也需要有大量的笨重的代码来处理一些少数突发情况。以下是我们必须应对的一些情况:

空的区间

假设你想描述一个从时间T=1开始的零长度时间区间(即空区间)。这对于闭-开区间来说很简单,简单地说就是[T, T),但对于闭-闭区间……就不是那么回事了。你可以尝试[T, T-1],但这有点问题,如果T是十进制数那么将无法实现。

按时间拆分

想象一下,你想对注册到你站点的用户按时间对他们进行分组,例如每小时分一组。这本质上是将一天中的24小时以1小时为区间“拆分”的问题。

通过[closed, open),你将得到一个很好的序列:[0, 1)、[1, 2)、[2, 3)、[3, 4)……[23, 24)。

通过[closed, closed],你将得到一个奇怪的序列:[0, 0:59:59]、[1,  1:59:59]……[23, 23:59:59]。请注意,这实质上会在你的整个系统中强制执行一个精确度。在0:59:59和1之间有用户注册么?这些用户会被落下。

计算区间的长度

每当涉及区间时,另一个常见的任务是计算它们的长度。

对于[a, b)很简单,它只是b-a。

但对于[a, b],会遇到一些边缘情况,例如如前所述[a, a-1]的长度可能为0,或者如果a < 1,则它可能为负。

你期望从正确实现的区间中获得的另一个属性是,将区间分成两半应该会产生两个小区间,这两个小区间长度加起来等于原始区间的长度。

例如,将一天中的小时数分成两个区间,应该得到长度为12的2个区间,即12+12 = 24。但使用[a, b]区间时会丢失此属性。


 3 
最后的想法


在我写这篇文章时,我发现了ol’ Edsger W. Dijkstra在1982年写的一篇简短的笔记,讲述了他为什么更喜欢[closed, open)区间。我不会用细节来烦你,但我只想说施乐帕克研究中心(Xerox PARC)的那些聪明人尝试了它们,发现[a, b]范围会导致错误和更复杂的代码。

我希望这篇简短的文章能让你认识到使用[closed, closed]区间的分险和陷阱。我猜测,人们有时喜欢使用[closed, closed]区间的原因,是它们看起来漂亮且对称,而且大部分时间能正常工作。

只有在极端情况下,它们才会开始崩溃。但这正是你应该评估设计好坏的关键:对其边缘情况进行测试。


推荐阅读:


分布式实验室策划的《Go实战集训营》正式上线了。这门课程通过5天线上集训,4个练习项目,40天课后辅导,和学员一起一步一步基于Go常用框架搭建起一个可以访问MySQL、Redis和第三方服务的Go服务,并进行线上调试,排查问题。培训重实战、重项目、更贴近工作,边学边练,1月7日正式开课,早鸟价倒计时3天!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存