很少有接口是完全从头开始创建的。它们会不断地进行调整,增加新的功能,修复bug或不一致的地方。为了更好地管理这个过程,采用版本管理系统来保留这些变更的过程是很有必要的。
版本管理的主要优点是形成关于什么时候包含了哪些东西的过程记录。这些记录可能是bug修复、新增的功能,乃至新引入的错误。
如果已知目前发布的接口版本是v1.2.3,而我们即将发布v1.2.4,其中修复了bug X,那么采用版本管理系统就可以更容易地对其进行描述,并生成发布说明,以告知用户这一情况。
这里有两类版本系统,也许会让人有点迷惑。其一是内部版本,这是对项目开发者有意义的版本,通常与软件的版本有关,一般都会有如Git这样的版本管理系统(亦称版本控制系统)的辅助。
内部版本是非常详细的,可能会涵盖非常细微的变化,包括小的bug修复。它的目的是能够检测到软件版本之间哪怕是最小的变化,从而可以让软件开发人员了解bug或代码的变更。
另一类是外部版本。外部版本是指要使用外部服务的人能够感知到的版本。虽然也可以做到和内部版本一样详细,但这样做通常对用户没有什么帮助,而且容易造成混乱的印象。
选择采用哪种版本管理机制,在很大程度上取决于系统的类型,以及预期的用户群体。专业技术型的用户会喜欢更多的细节,而非专业的用户则不然。
例如,某个内部版本可用于区分针对两个不同bug的修复,因为这对bug重现(亦称复现)很有用。而一个用于对外沟通的版本,则可以把它们都整合到一次“多个bug修复和改进”的版本中去。
另一个很好的例子是,当界面出现大的调整时,区分内外版本是很有用的。例如,某个网站的外观和体验的全新改版,可以使用“第2版界面”,但其改版过程可能是在多个内部新版本中实现的,由内部员工或特定的群体(例如,beta版软件测试者)对其进行测试。最后,等“第2版界面”准备好了,再提交给所有用户。
在描述外部版本的时候,也可称之为“市场版本”。
请注意,这里我们避免使用“发布版本”(release version)这一术语,因为它可能会产生误解。该版本仅用于对外沟通交流。
这个版本更多地取决于市场营销,而不是技术实现。
定义版本的一种常见模式是使用语义化版本管理(semantic versioning)。语义化版本管理约定了一种具有三个递增的整数的方法,这些整数具有不同的含义,按照其不兼容性依次降序排列如下:
vX.Y.Z
X是 主 版本号。所有主版本号的变化都意味着软件出现了向后不兼容的调整。
Y是 次 版本号。次版本号较小的改动说明可能会增加新的功能,但所有改动都会向后兼容。
Z是 修订 版本号。修订版本号表明只做了一些细微的改变,如bug修复和安全补丁,但并不会改变其接口。
版本号中开头的字符v是可选的,但保留它有助于表明这是一个版本号。
上述约定意味着为v1.2.15版本设计的软件可以在v1.2.35和v1.3.5版本上运行,但不能在v2.1.3版本或v1.1.4版本上运行。它可以兼容v1.2.14版,但可能有一些bug,后来被修复了。
有时,还可添加额外的细节来描述尚未准备好的接口,例如,v1.2.3-rc1(release candidate,候选发行版)或v1.2.3-dev0(development version,开发版)。
通常情况下,在软件准备发布之前,主版本编号被设置为零(例如,v0.1.3),将v1.0.0版本作为第一个公开发行的版本。
这种语义化的版本管理非常容易理解,并且能有效提供软件变化的相关信息。这种方式被广泛使用,但在某些情况下会有一些问题:
❍对于没有明确的向后兼容性的系统来说,严格采用主要版本的规则可能会很困难。这就是Linux内核停止使用严格意义上的语义化版本管理的原因,因为每一个版本都需要向后兼容,这导致永远不会更新主版本号。在这种情况下,一个主版本号可能会被“冻结”很多年,导致它不再是有用的参考信息。在Linux内核中,这种情况发生在2.6.X版本的内核上,主版本号一直保持了8年,直到2011年才发布3.0版本的内核,其间都没有出现任何向后兼容性方面的变更。
❍语义化的版本管理需要对接口进行非常严格的定义。就像有些在线服务经常出现的情况,如果接口随着新功能的出现而经常变化,那么次版本号的数量就会迅速增加,而修订版本号则几乎不会用到。
对于在线服务来说,这两者的结合会使得只有一个数字是有用的,这并不是一种很好的做法。例如,对于需要多个API版本同时工作的情况来说,语义化版本管理的效果更好:
❍API非常稳定,变化非常少,尽管有定期的安全更新。每隔几年就会有一次重大的更新。其中一个很好的例子就是数据库系统,如MySQL。操作性系统也是这样的。
❍API属于某个可能被多个支持环境使用的软件库。例如,某个与Python 2兼容的Python库将其设为v4版,与Python 3兼容的Python库设为v5版。这样就能根据需要让几个版本的软件库同时保持活跃。
如果系统实际上在同一时间只有某一个版本在运行,那么最好不要付出额外的努力来保持完全的语义化版本管理,因为相比所需的那些投入,这种努力得到的回报是不值得的。
与严格的语义化版本管理不同,还可以采取简化的版本管理。简化后的版本号没有语义化版本管理中的那些内涵,但它将是一个不断增长的计数器。这种版本号有助于团队协作,尽管它不涉及特定的含义。
这样的方式与编译器可以自动创建的构建版本号(build number)的理念是一样的,一个不断增加的数字可以将某个版本与其他版本区分开来,并作为参考。然而,普通形式的构建版本号用起来可能会显得有点单调。
最好是使用类似于语义化版本管理系统的结构,因为这样便于大家理解。但并不采用具体的规则来约束,而是用略宽松的方式:
❍对于一个新的版本,通常要增加修订版本号。
❍当修订版本号变得太高时(比如说,到了100、10,或其他某些数字),则增加次版本号,并将修订版本号设置为零。
❍有的时候,当相关的项目进展达到某些特定的里程碑阶段时,根据项目负责人的设定,可提前增加次版本号。
❍对主版本号也是如此。
这样就使得版本号以一致的规则增长,而且不必太在意其具体意义。
这种结构对于像在线云服务这样的系统非常有效,从本质上讲,这类系统需要一个增长的计数器,因为在同一时间只会部署单一版本的系统。在这种情况下,版本号本身的首要用途在于内部使用,不会像严格的语义化版本管理那样需要维护。