小 CL

为什么应该写小 CL?

小 CL 有如下优点:

  • 审核更快。 相对让审核者单独拿出30分钟审核大 CL,不如让他花费几个5分钟审核代码。对审核者而言,后者更容易。

  • 审核更彻底。 发生较大修改时,往往会反复审核、修改,审核者与开发者经常会因为过多的反复而在情绪上受到影响,以致于把精力放在修改上了,却忽略了 CL 中更重要的部分。

  • 引入新缺陷的可能性更小。 如果修改的内容比较少,自然审核人的效率会更高,开发者与审核者都更容易判断是否引入了新的缺陷。

  • 如果被拒绝,浪费的时间更少。 如果开发者花费了很大的精力开发了一个大 CL,直到审核的时候才知道整个开发的方向错了,那么之前的所有时间就全浪费了。

  • 更容易合并代码。 大 CL 在合并代码时会花费很长的时间,在合并时需要花费大量时间,而且在写 CL 期间可能不得不频繁地合并。

  • 更易于设计。 完善小 CL 的设计和修改要容易得多,多次微小的代码质量提高比一次大的设计改变更容易。

  • 减少阻塞审核的可能性。 小 CL 通常是功能独立的部分,你可能正在修改很多代码(多个小 CL),在发送一个 CL 审核时,同时可以继续修改其他的代码,并不会因为当前 CL 的审核没有完成而阻塞。

  • 更容易回退。 一个大 CL 开发的时间比较长,这意味从开发到代码提交这段期间,代码文件的变更会比较多。当回退代码时,情况会变得很复杂,因为所有中间的 CL 很有可能也需要回退。

请注意:审核者有权因为你的 CL 太大而拒绝它。 通常,他们会感谢你为代码做出的贡献,但是会要求你把它拆分多个小 CL。一旦写好了代码之后,要把它拆分成小 CL 通常需要花很多时间,当然,你也可能会花费大量时间与审核者争论为什么他应该接受这个大 CL 。与其如此,不如设计之初就保证 CL 尽量小。

如何定义“小”?

一般而言,一个 CL 的大小就应该是 独立功能的修改。这意味着:

  • 一个 CL 尽量最小化,它只 做一件事。通常它只是一个功能的一部分,而不是整个功能。总体而言,CL 太小或太大都不好,二者取其轻的话,太小稍微好点。可以与审核者一起讨论,找出大多比较合适。

  • CL 应该包含相关的测试用例

  • 审核者需要理解 CL 中包含的一切(除了以后可能要开发的功能之外),包括 CL 代码、描述、已存在的代码(或之前已经审核过的相关 CL )。

  • 在 CL 代码提交之后,无论是针对用户,还是针对开发人员,系统应该仍旧运行良好。

  • 如果代码难以理解,通常是因为 CL 还不够小。如果新增一个 API,同时应该同一个 CL 中附上这个 API 的使用方法,便于审核者理解如何使用,也方便以后的开发者理解。同时也可能有效防止提交的 API 无人使用。

没有直观的标准判断 CL “太大”应该符合哪些条件。 100行代码通常是一个合理的大小。1000行代码通常太大了,但也不能一概而论,这取决于审核者的判断。修改文件的个数也影响它的“大小”。在一个文件中修改200行可能没问题,如果这200行代码横跨50个文件,通常而言太大了。

记住,当你开始编写代码时,只有你最了解代码,而审核者通常不了解上下文。在你看起来很是一个合适大小的 CL,审核者可能会很困惑。毫无疑问,在写 CL 时,CL 的大小最好比自认为的还要小。审核者通常不会抱怨你的 CL 太小了。

什么时候可以有大 CL?

当然,也有一些例外情形,允许 CL 比较大:

  • 删除一个文件与修改一行没有太大区别, 因为它不会花费审核者太多时间。

  • 有时候,一个大 CL 可能是由可靠的自动代码重构工具生成的,审核者的工作主要是检查它是否做了它应该做的工作。虽然符合以上提到的注意事项(例如合并和测试),这类 CL 也可能比较大。

高效地编写小 CL

如果编写了一个小 CL,然后等待代码审核者审阅你的代码,待审核之后再编写下一个 CL,那无疑会浪费太多时间。所以,你应该找一些在审核是不会阻塞你的工作方式。例如,同时处理多个项目,寻找可以立即审视代码的审核者,面对面审核,结对编程,或以一种允许你立即继续工作的方式拆分 CL。

拆分 CL

当开始处理多个 CL 且这些 CL 有潜在依赖关系时,在深入编程之前,应在更高层次上考虑如何拆分合组织这些 CL。

除了让开发者更轻松地管理和组织 CL 之外,它还让代码审核者更轻松,从而让代码审核更高效。

以下是拆份 CL 的一些策略。

以队列的方式,一个接一个提交 CL

一种拆分 CL 的方式是:在不阻塞自己的代码的前提下,编写完一个 CL 后,提交它便于审核,然后立即 基于 第一个 CL开始写下一个 CL。大多数版本控制系统都允许你以这种方式工作。

以文件来拆分

另外一种拆分大 CL 的方法是:对 CL 中涉及的文件进行分组,这就要求不同独立功能的修改需要相应的审核者。

例如:你提交了一个 CL,这个 CL 修改了协议缓冲区,而且另外一个 CL 用到它。因此我们先提交第一个 CL,再提交第二个 CL,并让两个 CL 同时审核。此时,你应分别向两个 CL 的审核者告知另外一个 CL 的内容,以便他们知道上下文。

以代码和配置文件进行拆分。例如,你提交了2个 CL:其中一个 CL 修改了一段代码,另外一个 CL 调用了这段代码或代码的相关配置;当需要代码回滚时,这也比较容易,因为配置或调用文件有时候推送到产品比代码修改相对容易。

水平拆分

考虑创建共享代码或桩代码,用于帮助隔离技术栈各层之间的变化。这不仅有助于加快开发速度,而且还鼓励层之间的抽象。

例如:你创建了一个计算器程序,它包含客户端、API、服务和数据模型层。共享原型签名可以抽象服务层和数据模型层。同样,API桩可以将客户代码的实现从服务代码分离开,确保它们相互独立。类似的想法还可以应用于更细粒度的函数类级别的抽象。

垂直拆分

与分层的水平拆分方式正交,可以将代码拆分成更小的、全栈的、垂直功能。这些功能中的每一个都可以独立并行实现。这使得当某些轨道在等待审核或反馈时,另一些轨道可以继续前进。

回到水平拆分中的计算器示例。现在你想支持新的运算符,如乘法和除法。在实现时,你可以把乘法和除法实现为单独的垂直或子功能,即使他们有些功能重合,如 共享按钮样式或共享验证逻辑。

水平和垂直拆分

更进一步,你还可以把两种方式相结合,以如下表来实现。在下表中,每个单元格是一个独立的 CL。从模型(底部)开始开发,最后是顶部的客户端。

功能: 乘法
功能: 除法

Client

Add button

Add button

API

Add endpoint

Add endpoint

Service

Implement transformations

Share transformation logic with multiplication

Model

Add proto definition

Add proto definition

单独重构

在修改功能或修复缺陷的 CL 中,不建议把重构也加进来,而是建议把它放到单独的 CL 中。例如,修改类名或把某个类移到其他包内是一个 CL,修复这个类中的某个缺陷是一个 CL,不要把它们合并到一个 CL 中。把它们拆分出来更有利于审核者理解代码的变化。

有些代码清理工作,如修改某个类中的一个变量的名称,可以把它包含在一个功能修改或缺陷修复的 CL 中。那标准是什么呢?这取决开发者与审核者的判断,这种重构是否大到让审核工作变得很困难。

把测试代码包含到对应功能的 CL 中

CL 应该包含相关的测试代码。谨记,这里提到的的含以是 CL 应该聚焦于很小的功能,而不应简单粗暴地通过代码行数来衡量。

在 Google,所有的代码修改都应包含测试代码。

当新增或修改逻辑时,应同时更新测试。纯重构 CL(不涉及改变行为),应保证代码修改不会破坏已有的测试用例。理想情况下,在修改之前应该存在测试代码。如果不存在,则应添加。

然而,独立的测试修改可以放到单独的 CL 中,这与单独重构中的观点比较类似。它包含如下内容:

  • 为已存在的代码创建新的测试代码。

    • 确保重要的逻辑有对应的测试代码。

    • 增加对受影响代码进行后续重构的信心。例如,你想重构测试尚未覆盖的代码,则在提交重构 CL 之前提交测试 CL,用于验证重构前后,代码行为是否发生改变。

  • 重构测试代码(例如,引入 helper 函数)。

  • 引入测试框架代码(如,集成测试)。

不要破坏编译

如果同时在审核的有多个 CL,并且这些 CL 之间存在依赖关系,你需要找到一种方式,确保依次提交 CL 时,保证整个系统仍旧运行良好。否则,可能在提交某个 CL 之后,让系统编译错误。此时,你的同事在更新代码后,不得不花时间查看你的 CL 历史并回退代码以确保本地编译没有问题(如果你之后的 CL 提交出了问题,可能会花费更多时间)。

无法将其变小

在某些情形下,好像你没法然 CL 变得更小,这种情况很少发生。如果开发者经常写小 CL,那么他往往都能找到一种把 CL 拆得更小的方法。

如果在写代码之前就估计这个 CL 比较大,此时应该考虑是否先提交一个代码重构的 CL,让已有的代码实现更清晰。或者,与团队其他成员讨论下,看是否有人能帮你指出,怎样在提交小 CL 的前提下实现当前功能。

如果以上所有方法都试过,还是不可行(当然,这种情况比较罕见),那就先与所有的审核者沟通一下,告知他们你将会提交一个大 CL,让他们先有心理准备。出现这种情况时,审核过程往往会比较长,同事需要写大量的测试用例。需要警惕,不要引入新的 bug。

下一章: 如何处理审核者的评论

Last updated