3

原子性提交是持续集成与持续交付的必备技能

 2 years ago
source link: https://www.continuousdelivery20.com/blog/cr-atomic-submission/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

乔梁 | 2021-01-28

坚持少做,持续分解,坚持反馈,持续改善”是《持续交付 2.0 》一书中最为重要的四大工作原则。

它们在代码提交与 Code Review 中的应用就是:提交的原子性。

原子性,即 All or Nothing。当你修改完代码,想将其提交,进行Code Review 或合入到团队共有分支时,变更的内容应该包含且仅包含一个不可分割的任务( task )、特性( feature )、修复( fix )或重构( Refector ),同时还要尽可能的小。

术语:CL ,是 Change List 的缩写,代表一次代码变更的集合。

为什么 CL 一定要小

  • 代码评审得更快。对于评审者来说,与预留出 30 分钟的时间来评审一个大段 CL 相比,找到 5 分钟的时间来评审一小段 CL 要容易得多。

  • 评审得更彻底。如果是一个很大的 CL ,评论者和作者往往会面对大量的详细评论感到沮丧,有时甚至会出现非常重要的观点被遗漏的情况。

  • 不太可能引入bug。因为小的 CL 内容少,所以作者和评审者都更容易有效地分析 CL 的影响,并查看是否引入了bug。

  • 如果这个 CL 被拒绝,浪费的工作就更少。如果作者写了一个巨大的 CL ,而评审员却认为:CL 的总体修改方向就是错的,那么,作者可能就浪费了很多工作。

  • CL 更容易合入。一个大的 CL ,需要写很长时间,如果在此期间有很多人提交了代码,那么作者在合并时会有很多冲突,而且必须经常做合并操作。

  • 更容易得出好的代码设计。改进一个小的 CL 的设计和代码健康状况,要比改进一个大 CL 的所有细节容易得多。

  • 减少阻塞,保持工作的连续性。如果这个 CL 是一个完整自包含的原子任务,当你提交评审后,等待评审意见时,你仍旧可以继续下一个原子任务。

  • 让回滚更简单。如果一个 CL 较大,可能改动了较多的文件。当出现问题需要回滚时,我们很可能遇到的一个情况,即:这个大 CL 涉及多个文件,而这些文件在最初的 CL 和要回滚的 CL 之间,还被很多 CL 修改过。这会使回滚操作变得更加复杂(中间的 CL 可能也需要回滚)。

请注意,仅仅因为这次变更太大,审阅者就完全可以直接拒绝您。通常他们会感谢你的贡献,但要求你以某种方式把它变成一系列较小的变化。

然而,假如你已经写完一个大的CL,再去将它拆分成小的提交,可能就是一个很大的工作,或者需要花费大量的时间来争论为什么审阅者应该接受你的大变更。一开始写小的CLs比较容易。

CL 要小到什么程度

​一定要尽最大的努力,让一个 CL 的变更少于 800 行,大部分都应该在400行以下,最好能在 100 行以下

据调查报告显示,每小时最多可以评审 500 行代码,而一次 CR 最好少于 400 行,更容易最大化收益。

如何正确地缩减 CL 的大小

一般来说,CL的正确大小是一个独立的变化。这意味着:

  • 每个 CL 只做了一个非常小的改变,只解决了一件事。

  • 它通常只是一个特性的一部分,而不是一次完成整个特性。CL 太小和太大都不好。你要和你的团队一起,找出一致可接受的尺寸。

  • CL应包括相关的测试代码。如果这个 CL 的确无法加入自动化测试,则需要在提交信息中写上 TestPlan,以便评审者更容易理解。

  • 评审者需要了解关于本次 CR 的所有内容都在这个 CL 、CL 的描述、现有的代码库,或他们已经评审过的CL中。

  • 在本次 CL 合入到代码库以后,不应该破坏系统的正常构建和执行,以便其他开发人员可以正常工作。如果 CL 被部署到生产环境,那么它也不应该给用户带来麻烦。

  • 不能为了保证 CL 的小而让代码难以被评审者难以理解。如果你添加了一个新的 API ,那么就应该在同一个 CL 中包含这个 API 的用法,以便评审者能够更好地理解 API 的使用方式。这还可以防止合入未使用的 API 。

关于什么是“太大”,并没有一个业界公认的硬性规定。100 行 通常是一个 CL 的合理大小,1000 行通常就太大了,但这仍旧取决于评审员的判断。一个变更集所涉及的文件数也会影响它的“大小”。一个文件中 200 行的变更可能没问题,但这200行 散布于 50个文件中,通常就说明太大了。

请记住,尽管您从开始编写代码的那一刻起就与代码密切相关,但评审者通常没有和你一样的上下文知识。对于你来说,一个大小可以接受的 CL ,对你的评审者来说可能就是太大了。评审者很少会抱怨收到的 CLs 太小,如果你真的遵从了前面的建议。

在什么情况下,大的 CLs 是被允许的?

在下面几种情况下,大的 CL 也是可以接受的:

  1. 如果你将某个文件完全删除了,那么即使它有 1000 行代码,你也可以把它看作是 1 行。因为,评审者并不会花很长时间去评审这个被删除的文件的内容。
  2. 有时候,由一个你完全信任的自动重构工具自动生成了一个大的CL,而评审者的只需要验证说它们真的就是该任务想要的变更。这种自动化生产的 CLs 可以更大一些。当然,一定要记住,上面提到的那些要求(比如合并和测试)仍然适用。

如何让 CLs 以正确的方式变小

另一种拆分 CL 的方法是将需要不同评审者的文件分组,但这些文件也是相对独立的变更。

例如:一个 CL 是为了修改协议缓冲区,另一个 CL 是对使用该协议 的代码进行修改。但是,你必须在后一个 CL (使用新的协议)之前提交 第一个 CL(修改协议缓冲区),但它们可以同时被审阅。如果这样做,应该将编写的后一个 CL 通知两组评审者,以便他们有您所做更改的上下文。

另一个例子:一个 CL 用于代码更改,另一个用于对使用该代码的配置或实验进行变更;如果有需要的话,这也更容易回滚,因为配置/实验文件有时推送到生产环境的速度比代码变更要快。

将重构与新增和修复分离

通常最好是从特性修改或bug修复与重构放在不同的 CL 中。例如,移动和重命名一个类应该与修复该类中的 bug 放在两个不同的CL中。当每个 CL 是独立的时,评审者更容易理解它们引入的更改。

不过,在修改功能或修复缺陷的 CL 中可以包含一些小的清理,比如修复局部变量名。假如重构的动作比较大,将它包含在增加功能或修复缺陷的 CL 中,会使评审变得更加困难。

实在无法让 CLs 更小,怎么办?

有时你会遇到这样的情况,你的 CL 似乎必须很大。但是,这很少是事实。练习编写小型 CLs 的作者几乎总能找到将功能分解为一系列小更改的方法。

如果你认为,这次变更一定是一个大的 CL 时,请先考虑是否在它前面加上一个只有重构操作的 CL ,以便为后续能更清晰干净的修改代码铺平道路。与团队成员讨论一下,看看其他人是在使用小型 CLs 实现该功能有好的想法。

如果上面的这些选项都不行(这应该是非常罕见的),那么请事先征得评审者的同意,这样,他们就会被警告即将发生的事情。在这种情况下,我们需要花很长时间来检查这个过程,保持警惕,不要引入bug,并且在编写测试时应格外努力。

其它注意事项

不要让构建失败

如果有几个相互依赖的CL,那么我们就需要找到一种方法,来确保在提交每个 CL 之后,整个系统都能正常工作。否则,很可能你会在 CLs 提交之间,阻断其他开发人员的构建。如果您稍后的 CL 提交出现意外错误,甚至会让整个团队的工作阻滞更长时间。

如果一次 CL 提交了一个失败的测试用例,那么这个 CL 也不算是一个完整的工作单元。

CL 应该包含与其相应的测试代码

CLs应包括相关的测试代码。请记住,这里的“小”指的是概念上的小,即 CL 应该是聚焦的,而不是修改一个变更名,或者添加一个空函数体。

添加或修改代码逻辑的 CL 应该伴随着针对新行为的新的测试用例,或更新原有的测试用例。纯重构 CLs(不打算改变行为)也应该包含在测试中;理想情况下,这些测试已经存在。如果没有测试,,你应该添加上。

对于独立的测试修改,可以放在第一个单独的 CL 上,类似于前面的重构指南。这包括:

  • 使用新的测试用例验证预先存在的、已提交的代码。
    • 确保重要的逻辑被测试用例覆盖。
    • 增加对受影响代码的在后续重构中的信心。例如,如果要重构那些没有测试用例覆盖的代码,则在提交重构 CLs 之前,先提交测试 CLs,以验证重构前后测试行为是否保持不变。
  • 对测试代码进行重构(例如引入 helper 函数)。
  • 引入更大的测试用例(例如集成测试)。
参考链接:

谷歌工程实践


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK