语义版本化不会挽救您

2021-03-03 00:07:20

广泛使用的Python软件包密码术更改了其构建系统,以将Rust用于低级代码,这导致了情绪化的GitHub线程。除了1990年代的32位硬件爱好者之外,还有一个声望很高的派别规定维护者必须遵守语义版本控制,声称这样做可以避免所有麻烦。我不仅会告诉您为什么这是错误的,而且还会告诉您依赖语义版本控制会给您带来怎样的伤害。

献给愿意为我们其他人加油的亚历克斯(Alex)和保罗(Paul)。

让我们通过确定版本号的最终任务为阶段做准备:能够分辨一个实体的哪个版本比另一个版本新。这适用于所有内容,但我们将在此处重点介绍软件包。

软件社区已经决定将版本号解释为整数元组,并用句点分隔,从左到右优先。因此,2.0比1.10.0更新,而1.10.0比1.9.42更新。 Python社区有PEP 440对此进行了形式化。

就本文而言,这就是全部:版本号是软件版本的唯一且可排序的标识符。

多年来,好主意的人尝试为这些数字增加含义。可以说最受欢迎的是语义版本控制(SemVer)。您拥有MAJOR.MINOR.MICRO,并且可以保证,只要MAJOR不变(又称重大颠簸),一切都不会中断,并且您可以在不影响偏见的情况下更新依赖项。除非MAJOR为零,否则对维护人员而言意味着YOLO时间:任何事情都会发生。

不幸的是,在实践中,该方法的应用很差,无法兑现其承诺,并且给维护者和消费者都带来了意想不到的后果。

让我们从违约开始,当然还有一个xkcd:

如果有足够的API用户,那么合同中的承诺就无关紧要:系统的所有可观察到的行为都将取决于某人。

在实践中,这意味着:即使维护人员是纯洁的内心,极其勤奋并且对构成重大变更1的内容非常保守,也无法预测变更会影响用户的方式。

您想宣称3.2版与3.1版兼容,但是您怎么知道呢?您知道该软件基本上是因为您的单元测试而“运行”的,但是,如果您有任何故意的行为更改,则可以肯定地在3.1到3.2之间更改了测试。如何确定您没有删除或更改某人可能正在调用的任何功能?

在近20年的专业软件开发中,我观察到通过更新进行的无意破坏的数量远远超过了有意破坏的数量。

这种说法显然有些细微差别。我是从Python和Go程序员的角度写这篇文章的。而且,由于Python的动态特性,Python软件包很容易遭受由于意外副作用而导致的破坏。另一方面,我目前正在处理C库的两个次要发行版之间意外的不兼容性的后果。

从本质上讲,如果维护者不打算依靠更新而不中断,那就意味着依靠没有错误的软件。

这并不意味着SemVer不好或一文不值。知道维护者的意图很有价值,尤其是当事情破裂时。因为这就是SemVer的全部:变更日志的TL; DR。

不过,它的意思是,您不能依赖SemVer的语义,您必须将每次更新都视为潜在中断。如果微型版本的功能从未破坏过您的生产应用程序,那么您只需要等待一会儿即可。最终,您也将获得祝福–我保证。

从积极的一面来看,我看到主要的障碍不断出现而丝毫不影响我。重大颠簸只能告诉您是否存在有意进行的重大更改-但不会带来任何影响,因为它缺乏粒度。

但是必须做一些事情。做这些总比生活在对他们的恐惧中更好。

唯一负责您应用程序运行状况的人是您。如果您告诉客户他们无法访问他们的数据,您的客户将不会理解,因为不同大陆的某个少年没有严格按照您的要求坚持SemVer,从而破坏了您的工作流程。

固定主要版本号并不能避免您的损坏。您所希望的最好的办法是暂时推迟。推迟问题通常是一个可怕的想法,因为大多数问题只会变得更糟,而您忽略它们的时间就越长。

实际上,这意味着您需要积极主动,无论依赖项的版本方案如何:

Go的模块,Rust的Cargo和JavaScript的npm默认情况下会执行此操作。在Python中,我使用pip-tools,但是简单的pip冻结总比没有好。您必须将说出Flask的要求与说出Flask == 1.1.2的密码文件分开,再加上Flask的依赖关系以及理想情况下的哈希值。否则,每个版本都是彩票。

定期尝试将依赖项更新为最新版本。有一些工具可以帮助您。

如果某个软件包的单个版本由于意外故障而被破坏,并且维护人员打算在下一发行版中对其进行修复,请阻止考虑对该特定版本进行更新(例如Flask!= 1.1.2,但不包括Flask <1.1.2)。 )。

如果软件包有意进行了向后不兼容的重大更改并增加了其主版本,请阻止该主版本(例如Flask&lt; 2),但是-除非您有长期支持旧主版本的合同-否则,请着手进行改编。立即升级到新的主要版本。或寻找替代方案。

这是固定主要版本的唯一可接受的情况,并且严格是临时的。

根据流失的数量,您可以对开源软件包使用相同的过程。诸如Dependabot之类的工具将为您提供帮助。

就是这样。这是您必须执行的操作,以防止第三方程序包破坏您的项目,甚至破坏您的业务。多数对密码维护者感到生气的人都无法正确执行步骤2。

版本方案无法使它变得更容易。它只能帮助您确定损坏是否是故意的。

对于已将SemVer纳入其打包工具链2的Rust或Go等生态系统,也是如此。接下来我将列举的大多数意想不到的后果同样适用于它们。

维护人员经常觉得有义务进行SemVer。其中一些是由于不了解替代方案或认为“这就是完成方式”而自我强加的。其中一些是授权用户的要求的结果。尽管SemVer承诺自由(从技术上讲,只要您增加主要版本,您就可以破坏与每个发行版的兼容性!),实际上,它带来了更多的压力和工作。

我已经提到过,只要主要版本为零,维护人员就可以在SemVer中执行他们想要的任何事情。这导致许多维护者永远坚持自己钟爱的0ver。

SemVer标准很明确,因为适合生产的软件包应为1.0。不幸的是,从文化上讲,增加主要版本3的要求不高。因此人们坚持使用0ver,并且版本号在声明其语义的同时绝对没有任何意义。

因此,具有0ver版本并且同时声称已准备好投入生产的软件包是一个悖论。

我并不是说给项目蒙上阴影。我说这是为了证明SemVer不适合大多数项目,并且会增加维护者的倦怠感。

如此之多的人对加密维护者感到愤怒的原因是,他们坚信,如果仅加密技术遵守SemVer,他们就可以钉在主要版本上,而任何事情都不会中断。 4

正如我在上面显示的那样,“没有中断的部分”不过是对安全感和一厢情愿的错误认识。

不过,固定的问题更糟:大多数开源项目都没有能力维护多个主要分支。

因此,当您固定软件包的主要版本时,通常意味着一旦软件包破坏了其主要版本,您将不会获得任何更新。对于安全敏感的项目(例如密码术,还包括Web框架及其依赖项),这可能会带来灾难性的后果。

与npm不同,Python主流打包工具没有易受攻击版本的概念。您需要额外的工具或服务来实现这一目标。这意味着,如果您固定依赖项的主要版本,则您的应用程序最终将充满CVE,并且您将永远不会了解它。

如果您维护一个公共软件包并固定您所依赖项的主要版本,则可以过渡地对用户的应用程序执行此操作。

想象一下,一个应用程序依赖于出色的urllib3,而您的包也是如此。现在,如果您将urllib3固定为&lt; 2,则一旦urllib3的主要版本升级到2或更高版本,软件包的用户就无法再收到urllib3的更新。 6他们甚至可能没有意识到他们有多远。

另一方面,如果某个软件包的新主版本出人意料地破坏了您的软件包,则他们总是可以自己添加一个销钉(请参阅上面的第4步),直到修复您的软件包为止。但是他们没有实用的方法来移除您的图钉。

尽管缺少npm的安全功能,但某些Python打包工具默认情况下仍采用npm的主版本固定(^)。如果可能,请确保用手将其固定。

这是与最后一个相关的问题。在仅允许安装一个程序包版本的语言中,将您无法控制的另一个程序包的主要版本固定在您不知道另一个程序包的版本实际上已被破坏的情况下,您可能不必要地导致用户发生版本冲突他们无法修复自己。

至少您的用户会知道发生了什么。但是他们对此并不满意,这可能会给您的热带假期带来一些负面影响,这是由您维护FOSS项目所获得的数百万美元资助的。

如果您是维护者,并且喜欢SemVer作为对用户的额外服务,那就疯了!我不会在这里告诉您如何度过您的时间。在版本中添加语义是有价值的。

但是,我在这里告诉您,将SemVer应用于您的项目是完全可选的,如果它使您感到压力大,或者您永远陷入0ver土地(这意味着它确实使您感到压力大,但是您没有注意到或认可它) ,请考虑一些替代方案。

……无法防止破损。充其量,它可以推迟。更糟。

还有很多看起来像SemVer的高知名度项目,却不是:Linux,Python,Django,glibc…很好!

因此,请仅将版本号用于订购发布,对您的构建负责,不要骚扰维护人员以为您提供更多的自由劳动,充其量只是对您而言是微不足道的。

为了使自己摆脱算法的束缚,并能够为我的内容添加更多上下文,我开始了名为“ Hynek Did Something”的小批量,仅用于公告的新闻通讯。该帖子的公告/总监评论是其第一期,您可以在这里阅读。

如果您觉得有趣,希望您考虑订阅!如果您讨厌电子邮件,还可以在新闻通讯的主页上找到RSS源!

就像撰写本文时(2021年3月)已经达到版本53.1.0的setuptools维护者一样。结果是,任何一个具有单个数字版本号的项目都不会使用0.x转义阴影,那么它们的版本控制可能会被宽松地对待,以至于不能真正成为SemVer。 ↩︎

将foo = 1.0放到cargo.coml中只能匹配1.0系列的发行版。 Go中的主修课程意味着创建新的导入路径。这是如此痛苦,以至于Google都在为自己的项目而烦恼。 ↩︎

有趣的是,构建系统中的更改不影响公共界面并不能保证SemVer取得重大进展,但我们可以将其抛在一边。 ↩︎

还应注意,许多较小的项目从不提交CVE,而只是在解决过程中以静默方式解决安全问题。 因此,即使GitHub精美的新安全警报也无法为您提供帮助。 ↩︎ 在Rust,Go或JavaScript这样的语言中,这可以解决的问题不多,在这些语言中可以安装多个版本的软件包。 但是您仍然有一个问题,那就是您的程序包正在运行具有不安全版本的依赖关系。 ↩︎ 我的内容对您有帮助和/或愉悦吗?请考虑支持我!一切都有助于激发我创造更多东西的动力。 如果您不喜欢RSS,但希望收到有关新内容的通知(以及关于我的所作所为和原因的独家“导演评论”),请查阅我的极少量的“ Hynek Did Something”时事通讯。