返回博客

Changelog 不是给开发者写的

我在很多项目里见过同一种 CHANGELOG.md:密密麻麻全是 refactor(core): extract base classchore: update depsfix: typo in variable name。这些提交对维护代码确实有意义,但对一个想知道「这个版本改了什么」的用户来说,读完和没读一样。

这件事困扰了我很久,直到我意识到问题不在格式,在视角。

commit 是写给机器和未来自己的,changelog 是写给用户的

commit message 的受众是代码仓库的维护者——它描述的是「我改了哪里」。changelog 的受众是使用者——他们想知道的是「这对我有什么影响」。

这两件事不一样,却经常被混为一谈。于是出现了两种极端:一种是把所有 commit 原封不动 dump 进 changelog,全是技术噪音;另一种是每次发版都要花一两小时从头回忆「这版到底改了什么值得说」。

我现在的做法是在写 commit 时就分好层:

  • feat / fix / perf / security / breaking — 这些会进入 changelog,需要有用户可读的描述
  • refactor / style / test / chore / ci / build — 这些不进 changelog,只是工程内务

分层本身不是关键,关键是在写 commit 时就想清楚「这条信息的受众是谁」。一旦 commit 阶段做了这个区分,changelog 的生成就几乎是机械的筛选和翻译工作。

翻译的粒度

用户友好不等于模糊。「修复了一些 bug」是最差的 changelog 写法——用户无从判断这个修复是否与自己相关。

好的描述应该回答:这个变化改变了什么可见行为?举个对比:

  • 差:fix(backend): fix null pointer exception
  • 好:fix(backend): 修复题目列表在无数据时加载空白的问题

前者是代码层面的描述,后者是用户感知层面的描述。差别就在「从哪个视角出发」。

核心原则

写 changelog 条目时,先问自己:如果用户不看代码,他能从这句话里知道什么变了、为什么对他有用吗?能,就是好描述;不能,说明你还在用开发者视角写。

过滤噪音是最重要的一步

我见过有人把所有类型的 commit 都塞进 changelog,包括「更新依赖」「调整 CI 配置」「规范代码格式」。这些条目不仅对用户没有价值,还会稀释真正重要的变更,让用户找不到他们关心的内容。

过滤的原则很简单:这条变更是否改变了用户直接感知到的某个行为或能力? 如果改变的只是代码内部结构、构建流程、或者开发者工具链,那就不属于 changelog 的范畴。

实际操作上,我会在生成 changelog 之前先用 commit type 做一次粗筛,然后再对 featfix 类的条目逐一检查描述是否足够具体——有时候一个 feat commit 的 subject 写得太技术性,还需要在 changelog 里重新描述。

一句话心法

commit 是存档,changelog 是对话——两件事的受众不同,不要用同一套语言写。