1. 一次日常的 SaaS 发布
最常见的情形:少量对用户可见的变更,没有迁移,没有戏剧性事件。它之所以简短,是因为这次发布本身很小,而抵制住把它写得更“重要”的冲动,正是这项技能的大部分内容。
2026 年 8 月 20 日
新增
- 收件箱中的已保存视图。固定一次筛选条件,即可从侧边栏 重复使用。
改进
- 导出任务现在会在大型账户上报告进度,而不是看起来像 卡住了。
修复
- 被邀请的成员在首次登录前不会再看到空白的仪表盘。
## 2026 年 8 月 20 日
### 新增
- 收件箱中的已保存视图。固定一次筛选条件,即可从侧边栏
重复使用。
### 改进
- 导出任务现在会在大型账户上报告进度,而不是看起来像
卡住了。
### 修复
- 被邀请的成员在首次登录前不会再看到空白的仪表盘。
有效之处:每一行都是用户可能注意到的结果。没有版本号,因为产品是持续部署的,所以日期是读者唯一能与自己的经历对照的信息。
2. 包含弃用信息的 API 发布
API changelog 的读者在寻找一件事:自己的集成是否即将出问题,以及还剩多少时间。把这一点放在最前面,并附上日期。
Acme API 4.2 - 2026 年 8 月 20 日
破坏性变更
- 所有列表端点已移除
?page=。请使用上一次响应中的nextCursor值。2026 年 10 月 1 日之后,?page=将 返回 400。迁移步骤:acme.example/docs/pagination
新增
- 现在可以将 webhook 限定到单个项目。
改进
- 列表端点在拥有超过 10,000 条记录的账户上的响应速度 提升了约四倍。
## Acme API 4.2 - 2026 年 8 月 20 日
### 破坏性变更
- 所有列表端点已移除 `?page=`。请使用上一次响应中的
`nextCursor` 值。2026 年 10 月 1 日之后,`?page=` 将
返回 400。迁移步骤:acme.example/docs/pagination
### 新增
- 现在可以将 webhook 限定到单个项目。
### 改进
- 列表端点在拥有超过 10,000 条记录的账户上的响应速度
提升了约四倍。
有效之处:弃用说明明确指出了具体参数、替代方案、截止日期之后的失败方式,以及日期。读者只需一行就能判断这是否与自己有关。
3. 一次移动端发布
应用商店会显示被截断的“新变化”字段,而审核也可能让某个构建版本延迟数天。这两个事实共同塑造了这条条目。
iOS 3.4.0 - 2026 年 8 月 20 日
离线模式。无需网络连接即可打开、阅读和撰写草稿; 重新联网后,一切都会同步。
本次发布还包括
- 旧设备上的启动速度更快。
- 修复了从 Mail 打开分享链接时发生的崩溃。
## iOS 3.4.0 - 2026 年 8 月 20 日
离线模式。无需网络连接即可打开、阅读和撰写草稿;
重新联网后,一切都会同步。
### 本次发布还包括
- 旧设备上的启动速度更快。
- 修复了从 Mail 打开分享链接时发生的崩溃。
有效之处:一句话承载了整个发布,因为这正是商店列表所能展示的全部内容。日期是发布日期而非合并日期,因此与用户实际能够获得该版本的时间相符。
4. 一次安全修复
唯一一种“说得越少越正确”的条目。用户需要知道自己应该更新;除此之外,没有人需要精确到足以攻击尚未更新版本的描述。
2026 年 8 月 20 日
安全
- 加强了会话令牌的验证方式。使用自托管安装的账户应升级到 4.2.1 或更高版本。此问题经负责任地披露;没有被利用的 证据。详情:acme.example/security/2026-08
## 2026 年 8 月 20 日
### 安全
- 加强了会话令牌的验证方式。使用自托管安装的账户应升级到
4.2.1 或更高版本。此问题经负责任地披露;没有被利用的
证据。详情:acme.example/security/2026-08
有效之处:它在不点名端点、参数或技术手段的情况下,告诉读者是否需要采取行动。细节应留给按照自己节奏发布的安全公告,等人们有时间完成更新之后再说。
5. 反面示例是什么样子
这里的每一行在形式上都是真实的,也都是一个错误:
v2.3.7
- 合并了来自 feature/inbox-refactor 的 PR #482
- 将 lodash 从 4.17.20 升级到 4.17.21
- 修复了 MembershipCache.resolve() 中的一个竞态条件
- 各种 bug 修复和改进
- 重构了 SavedView 模型(感谢 Dave!)
## v2.3.7
- 合并了来自 feature/inbox-refactor 的 PR #482
- 将 lodash 从 4.17.20 升级到 4.17.21
- 修复了 MembershipCache.resolve() 中的一个竞态条件
- 各种 bug 修复和改进
- 重构了 SavedView 模型(感谢 Dave!)
问题所在:pull request 编号和分支名在仓库之外毫无意义。依赖更新和重构对用户没有可见影响,根本不应该出现。竞态条件说的是一个代码类名,而不是用户看到的症状。“各种 bug 修复和改进”正是人们说 changelog 毫无用处时引用的那句话。感谢的话应该留在提交记录里。
好的示例有什么共同点
- 它们描述的是结果,而不是实现方式。从未见过代码的读者仍然能判断该条目是否与自己相关。
- 它们省略了一些内容。依赖更新、重构、CI 变更和内部重命名都不存在,正是这种缺席让其余内容保持可读。
- 它们把代价最高的内容放在最前面。如果有什么东西坏了,它会是第一个标题,并附有日期。
- 它们以读者能够利用的方式标注日期:在用户能看到版本号的地方使用版本号,在看不到的地方使用日期。
- 它们刻意保持平淡。没有感叹号,没有营销式的形容词,也没有“我们很高兴地宣布”。阅读 changelog 的人在寻找信息,任何挡在信息前面的东西都会让他们感到不耐烦。
常见问题
changelog 应该使用什么格式?
keepachangelog.com 是最接近标准的做法,其分类名称(Added、Changed、Deprecated、Removed、Fixed、Security)也被广泛认可。这远不如各分类内部的措辞重要。用一致的格式写含糊的条目,比用松散的格式写具体的条目更糟。
我们应该多久发布一次?
以任何与你的发布节奏相匹配的频率发布,并保持一致。按每次发布来发布是最简单的规则。把一个月的发布内容打包成一篇文章,会让日后查找每一项具体变更变得更困难,而那恰恰是大多数人真正会阅读 changelog 的时刻。
changelog 应该放在我们自己的网站上,还是第三方页面上?
如果可以的话,放在你自己的网站上,因为流量和搜索价值会在那里积累,而且放在别人域名上的 changelog 与你的产品之间隔着一个链接,而不是产品的一部分。这正是把它作为你自己渲染的 feed 提供、而不是作为一个链接过去的托管页面提供的理由。
用户真的会阅读 changelog 吗?
一小部分人会定期阅读,而更大一部分人会在发现自己身边发生了某些变化的那一刻去搜索它。正是第二类人群,是应该写症状而非原因的原因:他们是在用自己的语言,搜索发生在自己身上的事情。