青菜年糕汤

一箪一瓢,一期一会。以文会友,以友辅仁。

2020年5月10日

向上兼容,向下兼容,向前兼容,向后兼容

作者:青菜年糕汤

发现好久没写与数据系统有关的技术类文章了。

兼容,这是一个在实际工程中至关重要,但在学院教育中几乎不会被讨论到的话题。

在英文中,好像一般不怎么用“向上、向下兼容”(upward/downward compatibility),而更常用更直观的“向前、向后兼容”(forward/backward compatibility),虽然这两套说法是一样的。

但在中文里,“向前”、“向后”的说法有些尴尬。

对照英文,很明显“向前”指的是向未来,“向后”指的是向过去。但脱离这个语境,在中文里,说到“向前”难免觉得是向“以前”,说到“向后”难免觉得是向“以后”。岂不乱套?

这不是篇语言学论文,我不能不用太多篇幅深究这个有趣的语言现象。简单说,大概是因为“前后”既能用来表示时间上的先后,又能表示动作的进退。如果我们向时间发展的方向走,这两者就是相反的。

写着写着我想到个好例子。如果不考虑修辞上的互文,“瞻前顾后”这个词,你直觉里是什么意思?仔细一想之后呢?

  • 看过去,看未来
  • 看未来,看过去
  • 看面前,看背后(方位上的)

投票链接

书归正传,来说到底什么叫向上、向下兼容。

“容”这个动作有个主体和客体——究竟是什么“容”什么呢?

我们可以认为主体是程序,客体是数据。“容”是在讨论程序正确解读数据的能力。

(但这也不绝对。比如当我们说一个操作系统支持一个应用程序,一个浏览器支持一个HTML网页,这里的客体是应用程序和网页,它们都是程序。但可姑且认为它们对操作系统和浏览器来说是数据。)

这个能力为什么会成问题?因为数据有格式,比如“5/10/2020”,如果不约定格式,可以理解为2020年5月10日,也可以理解为2020年10月5日,还可以理解为5除以10除以2020,不一而足。

如果不知道格式,数据就是一串毫无意义的零和一。甚至零和一也约定了隐含的格式——电平低到多少算是零?电平高到多少算是一?以及你怎么知道这就是二元的呢?只是这种“格式”目前不怎么被打破罢了。

一个程序理应能解读为它而产生的数据,因为它们用的是同一种格式。

在很多场景下,比如数据库,“为它而产生”的数据可能就是它自己产生的数据,用同一种格式更理所应当。

但随着技术的进步,功能的扩展,人们往往会想要改进格式。新的程序自然会用新的格式来解读和产生数据,但老的呢?

如果新的程序看不懂以前产生的数据,如果没更新过的程序对新程序产生的数据毫无头绪,那岂不是要等所有的程序都更新到最新版本,所有的数据都重写成最新格式,才能开始用吗?那会是多浪费和绝望啊!

“兼容”就应运而生。

“兼容”就是在讨论:一个程序在解读为它产生的数据之外,还能不能解读为其它程序产生的数据?

向下兼容,就是程序解读过去的数据格式的能力。如果支持向下兼容,之前存储的数据就不用重写一遍就能被新的程序正确理解。

在软件上,这往往挺容易实现。大不了就把旧版本的代码也放进新版本里,碰到旧版本的数据(但得要认得出来)就用旧版本的代码执行即可。

向上兼容,就是程序解读未来的数据格式的能力。如果支持向上兼容,不用升级程序也能在能力范围内解读新版本数据。

为什么强调是“能力范围内”呢?旧版本的程序理所当然没法支持新的格式带来的新功能,这里的期望只是它能对新数据正确运行原有的功能。

如何实现向下和向上兼容,是个与具体情况密切相关的话题。在这里就讨论一种极为常用的、把结构化的信息与序列化的数据相互转换的工具——Protocol Buffers。

它提供了一种语言,供开发者描述数据的格式,比如你可以规定一个数据结构:第一个字段是个字符串,表示姓;第二个还是个字符串,表示名;第三个字段是个整数,表示出生年份,等等。

它要求开发者在更新格式时,不能删、改原有的字段,只能增加新的字段。

这样,使用旧格式的程序,虽然不能完全看懂新格式数据的所有字段,但依然能找到它需要知道的信息,也即向上兼容。

同时,使用新格式的程序,依然看得懂旧格式数据里的所有字段,也即向下兼容。

当新格式的程序要读取旧格式数据中不存在的字段时,它要么会拿到一个默认值(在proto3中),要么会被告知数据不存在(在proto2中)。也就是说,程序不应该假设任何一个字段一定存在(尤其是proto3),要准备好不存在时该对应的操作。

你会发现,Protocol Buffers的规范只是让它自己不成为实现兼容的障碍,而最终兼容性的保证还是要靠开发者的意识。

比如说,当你的程序要对读到的数据稍做修改,再把它存起来或发给另一个程序, 你是不是一不小心丢弃了当前格式下尚未知的那些字段呢?