Hotdry.

Article

Conventional Commits 的错位焦点:为什么 Scope 比 Type 更重要

探讨 Conventional Commits 规范在代码审查和变更追踪中的实际局限,分析语义化版本控制在 CI/CD 中的真实价值,并提出以 Scope 为中心的替代方案。

2026-06-06systems

引言:规范化的代价

Conventional Commits 规范通过定义 type(scope): description 的格式,试图为提交信息建立统一标准。然而,这种以 type(如 fixfeatchore)为前缀的约定正面临越来越多的质疑。批评者指出,该规范将开发者的注意力引向了错误的方向 —— 过分强调变更的类型分类,却忽视了变更范围变更动机这两个对代码审查和长期维护更为关键的信息。

Type 优先的问题:冗余与误导

信息冗余

fix: prevent foo from bar'ing 这样的提交信息中,fix 前缀与描述中的 "prevent" 存在明显的语义重叠。正如评论者所言,"fix" 和 "prevent" 在多数语境下是同义词,强制添加 type 前缀并未增加新的信息量,反而占用了有限的标题空间。

相比之下,Linux 内核、FreeBSD、Git 本身以及 Go 语言项目采用的格式是 scope: description。例如:

  • net: fix ipv4 address parsing
  • cmd/go: add support for workspace mode

这种格式直接以受影响的组件开头,使开发者能够立即定位变更的边界,而无需先跳过 type 前缀。

分类的主观性

Type 的定义存在显著的模糊地带。chorerefactor 的边界在哪里?perf 改进是否应当视为 feat?这种分类的主观性导致团队成员在提交时产生不必要的认知负担,甚至为了通过 lint 检查而随意选择 type。

Scope 的价值:代码审查的导航图

快速定位影响范围

在大型代码库或 Monorepo 架构中,scope 提供了比 type 更关键的上下文信息。当审查者看到 auth:compiler: 这样的 scope 时,能够立即激活对应领域的知识储备,评估变更的潜在影响。

Type 前缀无法提供这种定位功能。fix: 告诉审查者这是一个修复,但修复的是核心模块还是边缘工具?这个信息只能通过查看文件变更列表或深入阅读描述才能获得。

与文件路径的互补

有观点认为,scope 信息可以从文件路径推导。然而,单次提交往往涉及多个目录的文件,而 scope 能够概括这些文件所属的逻辑组件。git log --oneline 的输出中,scope 前缀提供了比文件路径更紧凑、更语义化的导航线索。

被忽视的 "为什么"

Conventional Commits 规范的一个根本缺陷是它鼓励描述做了什么(what),而非为什么做(why)。规范文档中的示例大多关注技术实现的描述,但提交信息的真正价值在于记录决策背景。

当开发者六个月后在 git blame 中看到一条提交时,代码差异(diff)已经清晰地展示了 "做了什么",但 "为什么这样修改" 往往缺失。将变更与 issue tracker 中的工单关联(如 Fixes: #123)是一种方案,但更理想的做法是在提交正文中直接说明业务上下文和技术权衡。

CI/CD 自动化的迷思

语义化版本控制的局限

Conventional Commits 的一个主要卖点是驱动 CI/CD 流水线中的自动版本号递增:

  • fix: → patch 版本
  • feat: → minor 版本
  • feat!:BREAKING CHANGE: → major 版本

然而,这种自动化存在两个根本问题:

Revert 的复杂性:如果一个 breaking change 被提交后又被 revert,自动化工具会生成两次 major 版本递增 —— 一次是引入 breaking change,一次是 revert 本身。从语义化版本的角度,revert 也是一种 breaking change(撤销 API),但在实际发布周期中,这可能导致版本号跳跃而实际 API 并未改变。

受众错位:提交信息面向开发者,而 changelog 面向终端用户。自动生成的 changelog 往往充斥着技术细节("fix race condition in connection pool"),而用户关心的是功能变化("improved connection stability")。将两者混为一谈,既损害了开发者的代码考古体验,也降低了用户对版本更新的理解。

替代方案:Changesets

Changesets 提供了一种更为灵活的版本管理方式。与依赖提交信息不同,Changesets 要求开发者在提交变更时显式声明变更包(changeset),包含用户面向的描述和版本影响评估。这种方式:

  • 解耦了提交信息与版本发布的语义
  • 允许在发布前人工审核和编辑 changelog 条目
  • 支持 Monorepo 中多个包的独立版本管理

可落地的改进建议

1. 采用 Scope-First 格式

考虑采用 <scope>: <description> 的格式,将 type 信息融入描述的自然语言中:

# 而非 fix(auth): prevent token expiration
auth: fix token expiration issue

# 而非 feat(api): add rate limiting
api: add rate limiting to public endpoints

2. 使用 Git Trailers 存储元数据

对于需要机器可读的信息(如关联的 issue、breaking change 标记),使用 Git trailers 而非标题前缀:

Fix race condition in request handler

The previous implementation assumed requests would complete
within the timeout window. Under high load, this assumption
fails, causing connection leaks.

Fixes: #456
Breaking-Change: request timeout behavior modified

3. 区分提交信息与发布说明

建立明确的分工:

  • 提交信息:面向未来维护者,解释技术决策和实现细节
  • 发布说明(Release Notes):面向用户,描述功能变化和升级影响

在 CI/CD 流程中,使用 Changesets 或类似工具来管理发布说明的生成,而非直接从提交信息提取。

4. 版本号管理的人工介入

对于关键项目,考虑保留版本号的人工审核环节。自动化工具可以提供建议版本(基于变更分析),但最终决策权保留给维护者,以避免 revert 导致的版本号混乱和意外的 breaking change 发布。

结论

Conventional Commits 的价值不在于其具体格式,而在于它推动了团队对提交信息质量的重视。然而,当我们审视其实际效果时,scope 提供了比 type 更关键的上下文,而 "为什么" 比 "做了什么" 更值得记录。

在 CI/CD 集成的场景下,简单的自动化往往带来复杂的边界情况。Changesets 等工具展示了更成熟的替代方案 —— 它们承认发布管理需要人工判断,而非完全依赖提交历史的机械解析。

最终,提交信息规范的选择应当服务于团队的实际工作流程,而非让团队适应规范的限制。如果 Conventional Commits 的某些方面正在制造摩擦,那么调整或替换它,可能比强制遵守更有价值。


参考来源

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com