代码提交和审查规范
约定式代码提交(Git Commit)
概述
约定式提交规范是一种基于提交信息的轻量级约定。 它提供了一组简单规则来创建清晰的提交历史; 这更有利于编写自动化工具。 通过在提交信息中描述功能、修复和破坏性变更, 使这种惯例与 SemVer 相互对应。
提交说明的结构如下所示:
原文:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
译文:
<类型>[可选 范围]: <描述>
[可选 正文]
[可选 脚注]
提交说明包含了下面的结构化元素,以向类库使用者表明其意图:
- fix: 类型 为
fix
的提交表示在代码库中修复了一个 bug(这和语义化版本中的PATCH
相对应)。 - feat: 类型 为
feat
的提交表示在代码库中新增了一个功能(这和语义化版本中的MINOR
相对应)。 - BREAKING CHANGE: 在脚注中包含
BREAKING CHANGE:
或 <类型>(范围) 后面有一个!
的提交,表示引入了破坏性 API 变更(这和语义化版本中的MAJOR
相对应)。破坏性变更可以是任意 类型 提交的一部分。 - 除
fix:
和feat:
之外,也可以使用其它提交 类型 ,例如 @commitlint/config-conventional(基于 Angular 约定)中推荐的build:
、chore:
、ci:
、docs:
、style:
、refactor:
、perf:
、test:
等。 - 脚注中除了
BREAKING CHANGE: <description>
,其它条目应该采用类似 git trailer format 这样的惯例。
其它提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE)。
可以为提交类型添加一个围在圆括号内的范围,以为其提供额外的上下文信息。例如 feat(parser): adds ability to parse arrays.
。
示例
包含了描述并且脚注中有破坏性变更的提交说明
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files
包含了 !
字符以提醒注意破坏性变更的提交说明
feat!: send an email to the customer when a product is shipped
包含了范围和破坏性变更 !
的提交說明
feat(api)!: send an email to the customer when a product is shipped
包含了 !
和 BREAKING CHANGE 脚注的提交说明
chore!: drop support for Node 6
BREAKING CHANGE: use JavaScript features not available in Node 6.
不包含正文的提交说明
docs: correct spelling of CHANGELOG
包含范围的提交说明
feat(lang): add polish language
包含多行正文和多行脚注的提交说明
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123
约定式提交规范
本文中的关键词 “必须(MUST)”、“禁止(MUST NOT)”、“必要(REQUIRED)”、“应当(SHALL)”、“不应当(SHALL NOT)”、“应该(SHOULD)”、“不应该(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可以(MAY)” 和 “可选(OPTIONAL)” ,其相关解释参考 RFC 2119 。
- 每个提交都必须使用类型字段前缀,它由一个名词构成,诸如
feat
或fix
,其后接可选的范围字段,可选的!
,以及必要的冒号(英文半角)和空格。 - 当一个提交为应用或类库实现了新功能时,必须使用
feat
类型。 - 当一个提交为应用修复了 bug 时,必须使用
fix
类型。 - 范围字段可以跟随在类型字段后面。范围必须是一个描述某部分代码的名词,并用圆括号包围,例如:
fix(parser):
- 描述字段必须直接跟在 <类型>(范围) 前缀的冒号和空格之后。描述指的是对代码变更的简短总结,例如: fix: array parsing issue when multiple spaces were contained in string 。
- 在简短描述之后,可以编写较长的提交正文,为代码变更提供额外的上下文信息。正文必须起始于描述字段结束的一个空行后。
- 提交的正文内容自由编写,并可以使用空行分隔不同段落。
- 在正文结束的一个空行之后,可以编写一行或多行脚注。每行脚注都必须包含一个令牌(token),后面紧跟
:<space>
或<space>#
作为分隔符,后面再紧跟令牌的值(受 git trailer convention 启发)。 - 脚注的令牌必须使用
-
作为连字符,比如Acked-by
(这样有助于区分脚注和多行正文)。有一种例外情况就是BREAKING CHANGE
,它可以被认为是一个令牌。 - 脚注的值可以包含空格和换行,值的解析过程必须直到下一个脚注的令牌/分隔符出现为止。
- 破坏性变更必须在提交信息中标记出来,要么在 <类型>(范围) 前缀中标记,要么作为脚注的一项。
- 包含在脚注中时,破坏性变更必须包含大写的文本
BREAKING CHANGE
,后面紧跟着冒号、空格,然后是描述,例如:BREAKING CHANGE: environment variables now take precedence over config files 。 - 包含在 <类型>(范围) 前缀时,破坏性变更必须通过把
!
直接放在:
前面标记出来。如果使用了!
,那么脚注中可以不写BREAKING CHANGE:
,同时提交信息的描述中应该用来描述破坏性变更。 - 在提交说明中,可以使用
feat
和fix
之外的类型,比如:docs: updated ref docs. 。 - 工具的实现必须不区分大小写地解析构成约定式提交的信息单元,只有
BREAKING CHANGE
必须是大写的。 - BREAKING-CHANGE 作为脚注的令牌时必须是 BREAKING CHANGE 的同义词。
为什么使用约定式提交
- 自动化生成 CHANGELOG。
- 基于提交的类型,自动决定语义化的版本变更。
- 向同伴、公众与其他利益关系者传达变化的性质。
- 触发构建和部署流程。
- 让人们探索一个更加结构化的提交历史,以便降低对你的项目做出贡献的难度。
FAQ
在初始开发阶段我该如何处理提交说明?
我们建议你按照假设你已发布了产品那样来处理。因为通常总 有人 使用你的软件,即便那是你软件开发的同伴们。他们会希望知道诸如修复了什么、哪里不兼容等信息。
提交标题中的类型是大写还是小写?
大小写都可以,但最好是一致的。
如果提交符合多种类型我该如何操作?
回退并尽可能创建多次提交。约定式提交的好处之一是能够促使我们做出更有组织的提交和 PR。
这不会阻碍快速开发和迭代吗?
它阻碍的是以杂乱无章的方式快速前进。它助你能在横跨多个项目以及和多个贡献者协作时长期地快速演进。
约定式提交会让开发者受限于提交的类型吗(因为他们会想着已提供的类型)?
约定式提交鼓励我们更多地使用某些类型的提交,比如 fixes
。除此之外,约定式提交的灵活性也允许你的团队使用自己的类型,并随着时间的推移更改这些类型。
这和 SemVer 有什么关联呢?
fix
类型提交应当对应到 PATCH
版本。feat
类型提交应该对应到 MINOR
版本。带有 BREAKING CHANGE
的提交不管类型如何,都应该对应到 MAJOR
版本。
我对约定式提交做了形如 @jameswomack/conventional-commit-spec
的扩展,该如何版本化管理这些扩展呢?
我们推荐使用 SemVer 来发布你对于这个规范的扩展(并鼓励你创建这些扩展!)
如果我不小心使用了错误的提交类型,该怎么办呢?
当你使用了在规范中但错误的类型时,例如将 feat
写成了 fix
在合并或发布这个错误之前,我们建议使用 git rebase -i
来编辑提交历史。而在发布之后,根据你使用的工具和流程不同,会有不同的清理方案。
当使用了 不在 规范中的类型时,例如将 feat
写成了 feet
在最坏的场景下,即便提交没有满足约定式提交的规范,也不会是世界末日。这只意味着这个提交会被基于规范的工具错过而已。
所有的贡献者都需要使用约定式提交规范吗?
并不!如果你使用基于 squash 的 Git 工作流,主管维护者可以在合并时清理提交信息——这不会对普通提交者产生额外的负担。 有种常见的工作流是让 git 系统自动从 pull request 中 squash 出提交,并向主管维护者提供一份表单,用以在合并时输入合适的 git 提交信息。
约定式提交规范中如何处理还原(revert)提交?
还原提交(Reverting)会比较复杂:你还原的是多个提交吗?如果你还原了一个功能模块,下次发布的应该是补丁吗?
约定式提交不能明确的定义还原行为。所以我们把这个问题留给工具开发者, 基于 类型 和 脚注 的灵活性来开发他们自己的还原处理逻辑。
一种建议是使用 revert
类型,和一个指向被还原提交摘要的脚注:
revert: let us never again speak of the noodle incident
Refs: 676104e, a215868
代码审查规范(Code Review)
概要
审查别人的代码是一项艰巨的任务。你需要有效地处理它,同时牢记你是一名专业人员,不仅要对项目负责,还要对你的同伴负责。
尊重大家的时间
一个好的代码审查过程从尊重时间开始。理想情况下,你希望在首次提交后两小时内开始审查代码。这主要是为了尊重提交 PR 的人的工作。
例如,你的一位同伴要求你审阅他们的工作,他们可能会等待你审阅几分钟。但如果审查没有迅速完成,他们将开始处理其他事情。
你的同伴将创建一个新的功能分支并开始为新任务编写代码。如果在四个小时后,你查看他们的第一个代码并发现它有错误,你的同伴将不得不暂停他们现在正在做的事情来进行更改。像这样的上下文切换可能非常耗时。由于不同人的工作方式各有差异,他们可能需要相当一段时间才能在回归原始任务时恢复注意力。
如果几天或几周过去了,而你依然不检查代码,情况会变得更糟。这个时候,你的同伴估计连代码是干什么的都忘记了。让他们回忆之前代码时会浪费大量的时间,而且你的同伴将更容易引入错误,因为他们已经有一段时间没有处理该功能了。
始终尊重他人的时间和工作,努力成为最及时的审阅者,并能够意识到你的同伴等待审阅的时间非常宝贵。
始终提供建设性反馈
即使您正在审查代码,也要记住代码的背后是人。当你在谈论他们的工作时,尽可能避免触发其他人的情绪。
在对合并请求中的错误提供反馈时,采用建设性的心态并尝试使用积极的语言。
例如:
使用 “我建议” 或 “你可以通过做 Y 来改进 X”。
避免使用 “这样做” 或 “这是错误的,你为什么不做 Z?”
用书面文字表达意见是非常困难的——要注意有人可能会误解你的建议并认为是针对个人。
将改进项目代码作为你的使命,但仍要记住代码背后的人需要得到鼓励。除了保持积极的工作环境之外,这还可以大大有助于确保相同的错误不会成为下一个合并请求的一部分。
让自我远离代码审查
有时,你会发现自己不同意提交者对代码的实现或 PR 的其他审阅者给出的建议。
如:我们应该使用这个接口吗?这个名称是否适合这些变量?
在这种情况下,你和工程团队应该致力于营造一种以最佳论据获胜的文化。
请记住,仅仅作为高级开发人员,并不意味着你对某些初级编码人员的想法一定是正确的。向其他团队成员提供理由而不是感受以支持你的立场。如果你认为这是最好的方法,请坚持,但不要忘记提供你所说的一切背后的理由。例如,链接到文章或文档以加强你的观点。
如果争论变得激烈,请尝试安排立即通话并积极讨论你的想法。最后,要么你是对的,要么你会带着新的知识从讨论中走出来。
明确需要改进的地方
仔细检查你写的每条评论。一个简单的语义错误就有可能会给他人带来不必要的麻烦和理解成本。
一个混淆语义的例子:
类和函数的逻辑看上去没什么问题,但我建议修改下它的名称以更好的反应它所代表的功能和逻辑。
需要更改的是什么,类还是函数?究竟指哪个?为什么一个忙碌的同伴必须要通过询问你,以搞清楚你评论的真正意图?
另外,请注意明确指出你正在讨论的确切的代码所在行。
不能只要求代码能工作
要求代码的作者已经测试过代码是没问题的,但你也应该始终尝试运行或测试。两个脑袋总比一个好,所以检查引用分支并在本地拉下代码是更好的做法。
检查功能本身,但不要将自己局限于简单的测试,尝试去引发一些错误,或提出一些边缘案例,并将实际的代码库用于测试。
强化代码提交的最佳实践
参考 约定式代码提交 的内容,并在提交合并请求的时候,明确本次提交的内容或更改(例如,错误修复、修补程序、重构等),甚至是一些注意事项,以帮助审阅者更好的了解本次代码合并所涉及到方方面面。
严格审阅临时代码
偶尔会有人提交一些临时的代码。临时代码通常是一些临时修补程序或为某个模块能够正常工作而进行的临时重构。
在测试代 码时,不要因为是临时代码就降低审查标准。短期的解决方案有一种神奇的力量——可以很快将遗留代码变成肮脏的历史债务。如果你不想在未来处理更大的问题,那么无论是在审查一个巨大的功能合并请求还是最微小的临时代码,都要始终遵循相同的原则。
检查项目的卫星文件
每个大中型项目都会带来数百个围绕代码结构本身的文件。我们通常将它们称为卫星文件。其中包括文档、测试和配置文件。
请记住检查这些文件,因为它们通常与代码一样经常更新并且同等重要。例如,检查是否已为新功能添加测试用例(如果你的项目中有测试用例的话)。
如果一个函数或类已经被重构,那么文档也需要做相应的更新。
同样,构建产物需要能够反映对项目或单个文件所做的更改,而 CHANGELOG 也需要能够始终反映出项目执行的变更。
始终考虑长期可维护性
请大家注意,代码不仅仅是你写的那一行,而是这行代码应当如何更好的与项目本身结合在一起,并为项目本身提供正向或积极的作用。
比如:这种变化是否有利于项目的长期可维护性?我们是否因为它不可扩展而必须更改它?六个月之后,我和我的同伴是否还能够读懂这段代码?
合并请求绝不是一个孤立的事件。它是更广泛、更复杂系统中的一个环节。牢记最佳实践,并将其作为你决定某个变更是“批准”还是“拒绝”的指导思想。