停止使用 Conventional Commits

Hacker News Top 新闻

摘要

对 Conventional Commits 的批评,认为它优先考虑提交类型而非范围,这会误导贡献者,并且未能实现其承诺。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/05 17:09

# 停止使用约定式提交 来源:https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/ 你几乎肯定见过[约定式提交](https://www.conventionalcommits.org/en/v1.0.0/)(Conventional Commits)。它可能以你使用过的某个开源项目的更新日志形式露出过丑陋的面孔,也可能作为你贡献过的某个开源项目强制要求的提交格式出现。很多人对此推崇备至,而我则对此**深恶痛绝**。 尽管它被[大量](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md)[流行](https://electronjs.org/docs/development/pull-requests#commit-message-guidelines)[开源](https://contribute.freecodecamp.org/how-to-contribute-to-the-codebase/)[项目](https://jenkins-x.io/community/code/)[所采用](https://github.com/conventional-changelog/commitlint/blob/master/.github/CONTRIBUTING.md)[——包括](https://github.com/semantic-release/semantic-release/blob/master/CONTRIBUTING.md)[Angular](https://github.com/nuxt/nuxt/blob/main/CONTRIBUTING.md)[、Electron](https://github.com/vitejs/vite/blob/main/.github/commit-convention.md)[、freeCodeCamp](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md)[、Jenkins X](https://electronjs.org/docs/development/pull-requests#commit-message-guidelines)[、commitlint](https://contribute.freecodecamp.org/how-to-contribute-to-the-codebase/)[、semantic-release](https://jenkins-x.io/community/code/)[、Nuxt](https://github.com/conventional-changelog/commitlint/blob/master/.github/CONTRIBUTING.md)[、Vite](https://github.com/semantic-release/semantic-release/blob/master/CONTRIBUTING.md) 等——但约定式提交是一个**严重有害的标准**,它[鼓励关注错误的事情](https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#focus-failure),并且[未能兑现其承诺](https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#broken-promises)。 ## 关注点错误 https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#focus-failure 约定式提交承诺为提交信息添加语义含义,以帮助开发者和最终用户理解提交中的变更。然而,约定式提交以一种极为惊人的方式失败了。为了说明这一点,让我们看看一个约定式提交的构成。根据[约定式提交网站](https://www.conventionalcommits.org/en/v1.0.0/#summary),提交信息应格式如下: ``` <type>[optional scope]: <description> [optional body] [optional footer(s)] ``` 提交的主题行以 `<type>`(例如 `fix`、`feat`、`chore`、`docs` 或 `refactor`[^1])开头,描述变更的类型。之后是可选的 scope(范围),然后是一个描述。这种格式有一个重大缺陷:**类型被优先于范围**。这完全搞反了。 ### 范围 > 类型 https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#scope--type 变更的**范围**(变更的主题)是提交中最重要的部分。为了说明这一点,让我们思考以下每个利益相关方为何更关心变更的范围而非类型: - **贡献者:** 当你是某个项目的贡献者时,你经常需要阅读提交日志,以识别代码库中与某个代码区域相关的变更。原因有很多,包括: - 想了解自上次贡献以来发生了什么。 - 试图理解项目整体的动态方向。 - 在拉取或变基时,寻找可能与你进行中工作冲突的提交。 当你阅读提交日志时,你关注的是**哪些区域**被涉及。你根本不关心变更的**类型**,你在乎的是变更的**范围**。 - **调试人员:** 在调查 bug 时,你经常需要翻阅提交日志,看看哪些变更可能触及了与 bug 表现组件相关的区域。同样,范围是最重要的信息。变更的类型完全无用,因为 bug 可以在任何类型的变更中被引入。(我相信我们都经历过编写了一个修复 bug 的提交,却导致了另一个 bug。) - **事件响应人员:** 当生产环境宕机时,扫描提交日志查找中断发生前后的变更,是识别哪些区域可能引发问题的有效方法。此时,范围再次成为你手上最重要的信息。例如,如果你看到在入站 API 错误激增的关键时刻有一笔与 `auth` 范围相关的提交,那它很可能是问题的元凶。同样,类型无关紧要,因为任何变更都可能引入 bug。 那么约定式提交做了什么?它大大降低了范围的优先级,以至于将其设为**可选**!范围怎么可能是可选的?没有范围的提交就像没有主语的句子!更雪上加霜的是,约定式提交把**类型**提升到了提交信息的最前面。约定式提交完全搞错了范围和类型的优先级。 ### 类型既多余又具限制性 https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#type-is-redundant-and-restrictive 你可能会想:“就算优先级搞反了,提交类型至少还是很重要的吧?”对此我的回答是:“不”。提交的描述几乎总能告诉你变更的类型!以[这个提交信息](https://github.com/angular/angular/commit/ec138c3645f6e28829e69b6da2a839c248bb3bf0)为例: ``` fix(compiler): prevent namespaced SVG elements from being stripped ``` 即使你只有描述,很明显这是一个 bug 修复!提交主题行上的空间本已非常宝贵,浪费字符在类型上毫无帮助!更糟糕的是,它往往具有限制性。以[这个提交信息](https://github.com/angular/angular/commit/683172b39a602ac9ec15db69d22853433a67a084)为例: ``` refactor(core): Update webmcp support to use document.modelContext ``` 这个提交更新了 `core` 组件中的 `webmcp` 功能,使其同时支持 `document.modelContext` 和 `navigator.modelContext`。那么它算 bug 修复、重构还是新特性?我认为它三者都是!但同样,唯一真正重要的是这是对 `core/webmcp` 组件的一个变更。 **约定式提交从根本上关注错误的事情(提交类型),并且贬低了范围(这才是人们真正关心的)。** ## 破碎的承诺 https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#broken-promises 我们已经确定约定式提交的格式很糟糕,但它总该提供一些**好处**吧。让我们阅读一下[为什么使用约定式提交](https://www.conventionalcommits.org/en/v1.0.0/#why-use-conventional-commits)部分,看看这些理由是否站得住脚。 - **自动生成更新日志。** 这是约定式提交最大的承诺:你可以运行诸如 `git-cliff` 或 `conventional-changelog` 之类的工具,从上次发布以来的提交中生成更新日志。这甚至是个好主意吗?不!更新日志的受众与提交日志的受众完全不同!更新日志面向用户,用户关心的是理解版本之间的功能差异。他们关心的是**业务/功能**角度上的变化。而提交日志面向开发者,开发者关心的是阅读代码库随时间变化的“故事”。他们关心的是**范围**角度上的变化。如你所见,这是两种完全不同的粒度,任何将它们结合起来的尝试都会导致次优的结果。原因有很多: - 在任何中等复杂的项目中,一个值得注意的特性通常需要多个提交才能落地。落地该特性的过程(如提交日志所记录的)对开发者和贡献者很有价值,但对最终用户毫无用处。最终用户只关心新特性,而不关心它是如何构建的! - 正如 [Rich 指出的](https://richvdh.org/conventional-commits-considered-harmful.html#reverts-are-hard),回退提交对约定式提交来说是个问题。从开发者角度看,回退提交对于提交日志的叙事很重要;但对最终用户来说,一个被回退的变更等同于一个从未发生的变更。 - **自动确定语义版本升级(基于落地的提交类型)。** 这听起来不错,但软件工程的现实往往会严重干扰准确完成此任务的可行性。考虑以下情况: - **回退:** 设想一种情况,你引入的破坏性变更如此严重,以至于你不得不回退它?你的工具会检测到一个破坏性变更并递增主版本号,但事实上破坏已经被回退,并没有带来破坏性变更。 - **意外破坏:** 也许破坏很微妙,你在做出变更时并没有意识到这是一个破坏性变更。只有事后才发现它是破坏性的。你会错误地递增次版本/补丁版本,而实际上需要主版本号递增。 - **回溯性补救:** 假设你稍后添加了一个提交,它与之前一个破坏性提交组合后,产生的差异不再具有破坏性。与回退情况类似,工具会错误地识别出破坏性变更。 在这些情况下,你可以通过变基重写历史,但这往往会破坏工作流或被工作流阻止。而且,这向试图贡献项目的贡献者展示了一种修正主义历史,降低了提交日志所讲述故事的可靠性。 - **向队友、公众和其他利益相关者传达变更的性质。** 正如我们到目前为止所建立的,队友和公众对更新日志和提交日志有着截然不同的需求。约定式提交两者都没能解决。 - **触发构建和发布流程。** 这纯粹是个坏主意。假设你只对触及代码的提交运行自动化安全检查,然后有人创建了一个标题为 `docs: fix typos` 的特洛伊木马提交,实际上却向认证子系统引入了漏洞?显然,这种恶意活动希望能在代码审查中被发现,但自动化工具被绕过了,将识别问题的责任推给了人类。计算资源很便宜,只需使用 `git diff` 识别变更的文件(又是范围),然后基于此运行构建/发布流程。 - **让贡献者更容易为你的项目做贡献,允许他们探索更结构化的提交历史。** 更结构化?确实如此。但更容易贡献?完全不是(我们已经充分论证过了)。 约定式提交的所谓“卖点”没有一个是站得住脚的。此外,约定式提交在项目中应用起来也极其困难。你应该定义自己的一套“类型”,但几乎每个人都只是采用 `commitlint` 的默认值,而这些默认值往往与各个项目的具体情况不符。这个问题在企业环境中尤为尖锐,因为变更管理和审计要求通常强制在每个提交信息中包含工单号。`<scope>` 字段显然是放置它的地方,但这最终会用完全无用的工单号取代约定式提交中唯一有用的元数据。 ## 更好的方式 https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#a-better-way 那么你应该怎么做呢?效仿那些真正成功的软件项目,比如 Linux、FreeBSD、Git、Go 和 NixOS!这些项目有什么共同点?它们都使用以范围开头的提交信息(其中“范围”被定义为与项目实际相关的内容)。通常,在给定项目中使用什么范围是不言自明的。对于 Linux 内核,子系统是自然范围。对于 Go 项目,包路径是自然范围。对于使用微服务架构的项目,微服务名称是自然范围。 以下是一些项目及其提交格式指南的示例。 | 项目 | 格式 | 示例 | |------|------|------| | [Linux](https://www.kernel.org/doc/html/v4.14/process/submitting-patches.html) | `subsystem: description` | [`i2c: virtio: mark device ready before registering the adapter`](https://github.com/torvalds/linux/commit/1d774589f924) | | [FreeBSD](https://freebsdfoundation.org/wp-content/uploads/2020/11/Writing-Commit-Messages.pdf) | `prefix: Description` | [`linuxulator: Return EINVAL for invalid inotify flags`](https://github.com/freebsd/freebsd-src/commit/f77d37cffdf3) | | [Git](https://git-scm.com/docs/SubmittingPatches) | `area: description` | [`gitlab-ci: update macOS image`](https://github.com/git/git/commit/62319b49bbe7) | | [Go](https://go.dev/wiki/CommitMessage) | `package: description` | [`net/http/cookiejar: add godoc links`](https://github.com/golang/go/commit/517d4d3c7976) | | [nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md) | `pkg-name: description` | [`xwayland: 24.1.11 -> 24.1.12`](https://github.com/NixOS/nixpkgs/commit/7bf858875a54) | 不幸的是,尽管这种提交风格被有史以来最成功的一些开源项目所使用,但它似乎在“品牌”之战中落败了。我打算改变这一点。隆重推出 [scopedcommits.com](https://scopedcommits.com/)。该网站致力于倡导回归提交信息的理性,并将更新日志生成与提交日志管理的关注点分离。 ## 结论 https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/#conclusion 约定式提交所谓的优势实际上只是幻觉,整个行业并未从其作为一种标准的使用中获得任何切实的好处。然而,不幸的是,约定式提交似乎在开源项目中变得相当流行,因此人工智能也习惯性地默认使用它来生成提交信息。这导致反模式遍布的提交信息在各个项目中扩散。本文的目标是反对约定式提交的主导地位,并展示更好的提交信息结构方式。 但如果这篇文章仍未说服你停止使用约定式提交,我期待在评论区看到一场论战。 [^1]: 实际上,约定式提交规范并没有规定具体的类型,但常用的有 `fix`、`feat`、`chore`、`docs`、`refactor` 等。

相似文章

不要在提交信息中打广告

Lobsters Hottest

讨论为何不应在提交信息中自我推销或打广告,重点在于保持清晰且有用的提交历史。

为什么人们不善用Git?

Lobsters Hottest

一篇文章探讨为何许多开发者难以正确使用Git,涵盖常见错误如对合并冲突恐慌、提交过大以及分支管理不善,并分析根本原因。

我为什么仍然手写提交信息

Lobsters Hottest

作者认为,手写详细的 Git 提交信息对于解释变更背后的“为什么”很有价值,有助于个人理解和代码审查,即使在 AI 生成消息的时代也是如此。

始终追溯

matklad

本文探讨了通过版本控制追溯代码演变来阅读和理解代码的策略,重点强调了 `git blame` 的使用以及理解作者视角的重要性。

接受混乱的 git 历史

Lobsters Hottest

一篇博客文章,讨论两种 git 工作流理念——频繁提交与谨慎变基——并解释为何在大型团队中,鉴于团队的韧性,混乱的历史是可以接受的,文中还提到了 GitHub 的新堆叠 PR 功能。