下面是 Sumner Evans 的博文(原文地址是:https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/) ,让 Gemini 翻译了:
你几乎肯定以前遇到过 常规提交格式 。它可能在你使用过的某个开源项目的变更日志中出现过,也可能是你参与贡献的某个开源项目强制要求的提交格式。很多人对它赞不绝口,而我却 对它 深恶痛绝。
尽管许多流行的开源项目都在使用 传统 提交 , 但 它却 是 一个 糟糕的标准,它 鼓励 人们关注错误的事情 ,并且 未能兑现其承诺 。
焦点失败
Conventional Commits 承诺为提交信息添加语义,以帮助开发者和最终用户理解提交中所做的更改。然而,Conventional Commits 在这方面做得并不尽如人意。为了说明这一点,让我们来看一下传统提交的结构。根据 Conventional Commits 网站的 说明 ,提交信息的格式应如下所示:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
提交的主题行包含一个 <type> (例如\<commit> fix \、 feat \ chore <commit>\、 docs \<commit>\ 或 refactor [^1] )来描述更改类型。之后,可以指定更改范围,最后是更改描述。
这种格式存在一个重大缺陷: 类型优先于作用域 。这完全本末倒置。
范围 > 类型
变更范围(变更对象)是提交操作中最重要的部分。为了说明这一点,我们来探讨一下为什么以下每个利益相关者 都 更关心变更范围而不是变更类型:
- 贡献者: 作为项目的贡献者,您经常需要阅读提交日志,以识别代码库中与特定代码区域相关的更改。原因有很多,包括:
- 想了解一下自您上次贡献以来发生的事情。
- 试图了解项目的整体惯性在哪里。
- 查找在拉取或变基时可能与您正在进行的工作发生冲突的提交。
阅读提交日志时,你关注的是 哪些区域 被修改过。你并不关心具体修改了什么 类型 ,你关心的是 修改的 范围。
- 想了解一下自您上次贡献以来发生的事情。
- 调试人员: 在调查 bug 时,您通常需要查看提交日志,了解哪些更改可能影响了与 bug 出现的组件相关的区域。再次强调,范围是最重要的信息。更改类型完全没有用,因为任何类型的更改都可能引入 bug。(我相信我们都经历过编写 bug 修复程序却导致另一个 bug 的情况。)
- 事件响应人员: 当生产环境宕机时,扫描提交日志,查找宕机前后发生的变更,是识别问题根源的有效方法。此时,范围(scope)仍然是最重要的信息。例如,如果在
authAPI 错误涌入高峰期发现与该范围相关的提交,那么它很可能就是问题的罪魁祸首。再次强调,类型(type)无关紧要,因为任何变更都可能引入错误。
那么,常规提交到底做了什么?它把作用域的优先级降到了最低,甚至变成了 可选项 ! 作用域 怎么会变成可选项呢?提交没有作用域就像句子没有主语一样!更糟糕的是,常规提交还把 类型 放在了提交信息的最前面。常规提交完全搞错了作用域和类型的优先级。
类型是冗余且具有限制性的
你可能会想:“所以这听起来有点本末倒置,但提交类型至少还是很重要的,对吧?”对此我的回答是“不”。提交描述几乎总是应该告诉你更改的类型! 例如,请看 以下提交信息:
fix(compiler): prevent namespaced SVG <style> elements from being stripped
即使只有描述,也能明显看出这是个 bug 修复!提交主题的空间本来就很宝贵,再浪费字符写类型说明毫无意义!而且,这样做往往比没用更糟糕,还会造成限制。 例如,看看 这条提交信息:
refactor(core): Update webmcp support to use document.modelContext
这次提交更新了 webmcp 组件的功能, core 使其同时支持 \and\ document.modelContext 和 \or\ navigator.modelContext ,所以这算是 bug 修复、重构还是新功能呢?我认为三者兼具!但话说回来,真正重要的是组件本身发生了变化 core/webmcp 。
传统的提交方式从根本上关注了错误的事情(提交类型),贬低了范围(这才是人们真正关心的事情)。
违背承诺
所以我们已经确定,常规提交格式很糟糕,但它肯定也有 一些 好处。让我们阅读 “为什么要使用常规提交” 部分,看看其中的理由是否合理。
- 自动生成变更日志。
这是 Conventional Commits 的最大优势:你可以运行像git-cliff 或 conventional-changelog 这样的工具 ,根据你上次发布以来的提交记录生成变更日志。但这真的是个好主意吗?当然不是!变更日志的受众与提交日志的受众完全不同!
变更日志是面向用户的,用户关心的是了解不同版本之间的功能差异。他们关心的是从 业务/功能 角度来看发生了哪些变化。
提交日志是面向开发人员的,开发人员关心的是代码库随时间推移的变化历程。他们关心的是从 作用域 角度来看发生了哪些变化。
如您所见,这是两种完全不同的谷物,任何试图将它们混合的尝试都会导致品质不佳的结果。原因有很多:- 在任何中等复杂程度的项目中,实现任何重要的功能都需要多次提交。功能实现的过程(由提交日志记录)对开发者和贡献者来说很有价值,但对最终用户来说毫无意义。最终用户只关心新功能本身,而不是它的实现方式!
- 正如 Rich 指出的那样 ,回滚操作对于常规提交来说存在问题。从提交日志的角度来看,回滚提交对开发者来说很重要,但对最终用户而言,被回滚的更改就等同于没有进行过更改。
- 在任何中等复杂程度的项目中,实现任何重要的功能都需要多次提交。功能实现的过程(由提交日志记录)对开发者和贡献者来说很有价值,但对最终用户来说毫无意义。最终用户只关心新功能本身,而不是它的实现方式!
- 自动确定语义版本号提升(基于已合并的提交类型)。
这听起来不错,但软件工程的实际情况往往会严重影响这项任务的准确性。请考虑以下情况:- 回滚: 想象一下,你引入的破坏性变更实际上影响巨大,以至于你不得不回滚它?你的工具会检测到这一破坏性变更,并递增主版本号,即使实际上该变更已被回滚,不再存在任何破坏性变更。
- 意外破坏: 也许破坏很细微,你在进行更改时并没有意识到这是一个破坏性更改。只有在事后才发现它造成了破坏。例如,你可能会在需要提升主版本号时错误地增加了次版本号/补丁版本号。
- 追溯性修复: 假设你之后添加了一个提交,该提交与之前导致破坏的提交组合后,产生的差异不再是破坏性的。与回滚情况类似,工具会错误地将其识别为破坏性变更。
在这种情况下,虽然可以使用变基操作来改写历史记录,但这通常会破坏工作流程或被工作流程阻止。此外,它还会向试图为项目做贡献的贡献者呈现一个经过修正的历史记录,从而降低提交日志所反映的信息的可靠性。
- 回滚: 想象一下,你引入的破坏性变更实际上影响巨大,以至于你不得不回滚它?你的工具会检测到这一破坏性变更,并递增主版本号,即使实际上该变更已被回滚,不再存在任何破坏性变更。
- 向团队成员、公众和其他利益相关者传达变更的性质。
正如我们之前所讨论的,团队成员和公众对变更日志和提交日志的需求截然不同。传统的提交方式无法满足这两种需求。 - 触发构建和发布流程。
这完全是个馊主意。假设你只对修改代码的提交运行自动化安全检查,然后有人创建了一个名为“tmp”的木马提交,docs: fix typos该提交实际上在身份验证子系统中引入了漏洞?显然,这种恶意活动在代码审查中应该会被发现,但自动化工具却被绕过了,最终只能由人工来识别问题。
计算成本很低,只需git diff用于识别已更改的文件(再次强调作用域),并基于此运行构建/发布流程即可。 - 通过允许人们探索更结构化的提交历史记录,使他们更容易为您的项目做出贡献。
结构更清晰了,没错。让贡献变得更容易了吗?完全没有(我们已经详细论证过了)。
传统承诺的“卖点”没有一个站得住脚。
传统提交方式也很难应用于项目。虽然它要求开发者定义自己的“类型”,但几乎所有人都直接采用 commitlint 的 默认设置,而这些默认设置往往与具体项目的具体情况不符。这个问题在企业环境中尤为突出,因为变更管理和审计要求通常规定每个提交消息都必须包含工单编号。虽然该 <scope> 字段显然是放置工单编号的最佳位置,但这最终却用一个完全无用的工单编号替换了传统提交方式中唯一有用的元数据。
更好的方法
那么,你应该怎么做呢?不妨借鉴 Linux、FreeBSD、Git、Go 和 NixOS 等真正成功的软件项目!这些项目有什么共同点?它们都使用带有 scope 前缀的提交信息(其中“scope”的定义与实际项目相关)。通常,特定项目应该使用哪个 scope 是显而易见的。对于 Linux 内核,子系统是自然的 scope。对于 Go 项目,包路径是自然的 scope。对于使用微服务架构的项目,微服务名称是自然的 scope。
以下是一些项目及其提交格式指南的示例。
| 项目 | 格式 | 例子 |
|---|---|---|
| Linux | subsystem: description |
i2c: virtio: mark device ready before registering the adapter |
| FreeBSD | prefix: Description |
linuxulator: Return EINVAL for invalid inotify flags |
| Git | area: description |
gitlab-ci: update macOS image |
| 去 | package: description |
net/http/cookiejar: add godoc links |
| nixpkgs | pkg-name: description |
xwayland: 24.1.11 -> 24.1.12 |
| Node.js | subsystem: description |
stream: fast-path stateless transform flush results |
遗憾的是,尽管一些最成功的开源项目都采用了这种提交方式,但它似乎在品牌推广方面已经败下阵来。我打算改变这种现状。隆重推出 scopedcommits.com 。该网站致力于倡导回归提交信息的清晰规范,并将变更日志的生成与提交日志的管理分开。
结论
传统提交模式所谓的优势实际上只是假象,业界并未从将其作为标准中看到任何实际益处。然而,不幸的是,传统提交模式似乎在开源项目中相当流行,因此人工智能似乎也习惯于默认使用它来编写提交信息。这导致了充斥着反模式的提交信息在项目中传播。
本文旨在挑战传统提交信息的垄断地位,并论证构建提交信息有更优方案。但如果本文未能说服你放弃使用传统提交信息,我期待在评论区看到一场激烈的辩论。
[^1]: 严格来说,Conventional Commits 规范只定义了 fix \commit\ 和 \ feat commitlint\,其他类型则留给各个项目自行指定。然而,大多数项目最终都使用 commitlint 定义的 类型,所以我在此列表中包含了其中一些




