工程实践

体验日志文件格式:JSON、YAML,还是干脆用 Markdown

阅读约 1 分钟

大多数团队把体验日志起步成一份 Markdown 文件,因为这是阻力最小的一条路:在 pull request 的 diff 里能读,在 GitHub 上不用渲染任何东西就能读,而且对任何写过 README 的人来说都很眼熟。这个选择运作得很好,一直好到有什么人以外的东西需要读这份文件为止,一个页面、一个小组件、一封邮件摘要,从那一刻起,这个格式就不再是免费的了。体验日志自动化一般性地讲了结构化的要求,类型、日期、正文和链接;而这篇讲的是哪种文件格式真正能交付出那种结构,以及每一种格式为了到达那里各自要付出多少代价。

一份普通的 Markdown 体验日志到底有什么问题

没什么问题,直到有什么东西需要把它反解析回字段为止。一个标题、一个日期,加上下面一个项目符号列表,对人来说读起来毫不费力,但要可靠地解析它却真的很难,因为 Markdown 根本没有模式:日期可能在标题里,可能加粗放在第一行,也可能在一条旧记录里干脆就不存在,而这些变体的每一种,都是人能正确读出来、解析器却读不出来的合法 Markdown。自动化 Markdown 体验日志的团队,通常最后都会写出一个基于正则表达式的自制解析器,只要某条记录的格式稍微偏离一点点就会崩掉,而这种情况经常发生,因为写的时候没有任何东西在强制保持一致。

一种结构化格式到底能给你带来什么

一份保证,保证每条记录都有同样的形状,而这份保证是在记录被写下的那一刻就被检查过的,而不是在被读取的时候被人猜出来的。一份定义了模式的 JSON 或 YAML 文件,类型、日期、版本、受众、正文、链接,一旦缺了某个必填字段就会大声报错失败,就跟一个严格的 API 响应会做的一样;而一份 Markdown 文件只会把那里有的东西原样渲染出来,不管它对不对。这种差别是看不见的,一直到某天一个脚本需要每条记录的日期去给一条信息流排序,却发现一半的记录把日期放在了别的地方。

# CHANGELOG.yml
- date: 2026-09-05
  type: breaking
  version: v2
  audience: api
  body: "POST /invoices now rejects a currency mismatch instead of silently converting."
  link: /blog/api-changelog/

这是不是说那份人类可读的文件必须消失

不是,而且试图让一份 YAML 或 JSON 文件同时兼职扮演人在 pull request 里读的那份东西,通常是在反方向上犯了个错误:审阅一段嵌套 JSON 的 diff,比审阅一句散文要糟糕得多,而一个必须在脑子里把数据结构解析一遍才能抓出一个措辞错误的审阅者,终究会是一个渐渐不再去抓措辞错误的审阅者。这两种格式完全可以共存:结构化数据是自动化流水线读取的那个真相来源,而一份生成出来的 Markdown 或 HTML 渲染,才是人真正去审阅、去读的东西,它是从那份结构化文件产生出来的,而不是手动在旁边另外维护的。

格式原样是否人类可读不写自定义代码能否被机器解析常见的失败方式
Markdown能不能不一致的记录形状会弄坏那些朴素的解析器
JSON差能啰嗦;手动编辑很容易改出一份无效的 JSON
YAML还行能对空白很敏感;一个缩进错误是一次安静的解析错误,而不是一次响亮的

哪种结构化格式手动编辑起来其实更容易,JSON 还是 YAML

是 YAML,对任何手写记录而不是通过一个生成器去写的人来说都是。因为它省掉了 JSON 对每一个字符串和每一个嵌套对象都要求的引号和括号匹配。代价在于,YAML 对空白的敏感性会以一种 JSON 括号不匹配通常不会出现的方式安静地失败:一个 JSON 解析器会直接拒绝格式错误的输入,而一个 YAML 解析器却可能接受一份缩进错了的文件,然后就那么把它解析成了错误的结构,这是一种更糟的失败,因为没有任何东西会告诉你这件事发生了。如果记录永远只由一个脚本来写,这种代价基本上就消失了,JSON 更严格的解析方式也就变成了更安全的默认选择。

一个体验日志页面是不是需要一种自己专属的结构化格式,和喂养它的那份文件分开

不需要一种分开的格式,而是同一份数据被渲染成了不同的样子。体验日志页面讲的是怎样通过一条 JSON feed 和 schema.org 标记,让页面本身变得机器可读;那条 feed 是生成出来的输出,而不是一个需要和底层文件保持同步的第二个真相来源。在两个地方手动维护结构化数据,一份源文件和一个页面的 feed,正是这两者最终走散的原因,所以这里做出的文件格式决定,应该成为唯一那份东西,让下游的一切,页面、小组件、邮件,都是从它生成出来的,而不是被手动复制过去的。

把一份既有的 Markdown 体验日志转换成结构化格式,这个迁移成本值不值得

通常只有当自动化真的成为目标之后才值得,在此之前不值得。一个把 Markdown 文件发布在 GitHub README 里的单人项目,并没有真正的自动化需求,把它转成 YAML 除了买来一堆繁文缛节之外什么都得不到。这个转换会在超过一个下游消费者,一个页面、一封摘要邮件、一条公开的 feed,需要读取同一份数据的那一刻开始自己收回成本,因为那正是 Markdown 解析器的不一致性开始产出肉眼可见的错误输出,而不只是维护起来烦人的那个临界点。

FAQ

一份 Markdown 体验日志能不能在不彻底换格式的前提下变得可解析? 可以部分做到,靠 frontmatter:在每条记录开头放一小块 YAML(日期、类型、版本),旁边跟着一段用于散文的 Markdown 正文。这样能拿到解析器需要的那些结构化字段,又不用把整条记录都硬塞进 JSON 或 YAML,对一个还没准备好做完整迁移的团队来说,是个合理的中间点。

文件格式对 SEO,或者对一个体验日志页面的排名,到底有没有影响? 没有直接影响。搜索引擎读的是渲染出来的页面,不是源文件,所以文件格式对它们来说是不可见的;真正对页面本身重要的,是它自己是不是机器可读,这和是什么生成了它,完全是两个不同的问题。

每一条体验日志记录是不是都应该经过同一份文件,还是可以按类型拆到多份文件里? 一份文件更简单,直到记录量大到让 diff 或审阅变得不方便为止;按年份或按类别拆分,是一个合理的泄压阀,一旦单一文件的 diff 大到没法合理审阅的地步就可以用,但这会在下游的任何东西能把「所有记录」当成一份列表读取之前,多加一个合并的步骤。

有没有一种像 RSS 那样存在标准的体验日志文件格式? 没有一种被广泛采纳的。Keep a Changelog 提出了一套 Markdown 约定,还有好几个工具都有自己的格式;一个 changeset 就是一份带 YAML frontmatter 的 Markdown 文件,写明对应的包和版本升级幅度,也就是上面讲过的 frontmatter 模式。但这些都不是那种别的工具能开箱即用直接读取的格式,不像 RSS 阅读器普遍都能理解 RSS 那样。


本文的技术内容未经独立审核。如有错误,请告诉我们,我们会更正。

changeloop 相关页面: changelog 工具对比, changelog 生成器

changeloop
打造闭环 changelog 的团队。用户提出需求,你的团队交付,提出需求的人得知结果。