Golden master 能用很少的代码保护复杂输出,但它的价值取决于输出是否确定。这篇用一个真实的小程序量出:哪些行会变、换环境会变多少、加固后变成什么,以及自动更新 master 的风险。
master.txt(17 行,509 字节)的前 8 行:
== INVOICE ==
generated: 2026-10-03T20:23:51.697400Z
id: 23def6f1-56e5-463c-9166-6a88163abd6e
date: March 8, 2026
zone view: 20:00
widget 3 x 9.99 = 29.97
gadget 12 x 1,234.50 = 14,814.00
thing 1 x 0.30 = 0.30
同一台机器、同样的参数再运行 6 次,与 master 完全一致的有 0 次
第 1 次运行与 master 的差异(共 4 行变化,前 4 行):
-generated: 2026-10-03T20:23:51.697400Z
-id: 23def6f1-56e5-463c-9166-6a88163abd6e
+generated: 2026-10-03T20:23:51.776858Z
+id: 0dabb150-b8da-4ad3-bd4c-7372f1a82de6 共 17 行,其中 2 行在 12 次运行里出现了不同取值:
第 2 行:12 种取值,例:generated: 2026-10-03T20:23:52.279745Z
第 3 行:12 种取值,例:id: 6badfd94-32bd-48bc-8dd0-224cec99e332
其中 tags 行(用身份哈希的 HashSet)出现了 1 种顺序: ['tags: [epsilon, alpha, beta, gamma, delta]']
byName 行(String 键的 HashMap)出现了 1 种顺序: ['byName: {widget=3, title case=2, gadget=12, thing=1}'] [基线] 与基线(忽略 generated / id 两行)相比 0 行不同
[德语 de_DE] 与基线(忽略 generated / id 两行)相比 16 行不同:
-date: March 8, 2026
+date: 8. März 2026
-widget 3 x 9.99 = 29.97
-gadget 12 x 1,234.50 = 14,814.00
-thing 1 x 0.30 = 0.30
-title case 2 x 20.00 = 39.99
-TOTAL 14,884.26
+widget 3 x 9,99 = 29,97
[土耳其语 tr] 与基线(忽略 generated / id 两行)相比 18 行不同:
-date: March 8, 2026
+date: 8 Mart 2026
-widget 3 x 9.99 = 29.97
-gadget 12 x 1,234.50 = 14,814.00
-thing 1 x 0.30 = 0.30
-title case 2 x 20.00 = 39.99
-TOTAL 14,884.26
-title: TITLE / info
[美国西部时区] 与基线(忽略 generated / id 两行)相比 4 行不同:
-zone view: 20:00
+zone view: 05:00
-tags: [epsilon, alpha, beta, gamma, delta]
+tags: [beta, gamma, alpha, delta, epsilon]
[file.encoding=ISO-8859-1] 与基线(忽略 generated / id 两行)相比 2 行不同:
-charset: UTF-8
+charset: ISO-8859-1
[阿拉伯语 ar] 与基线(忽略 generated / id 两行)相比 16 行不同:
-date: March 8, 2026
+date: 8 مارس 2026
-widget 3 x 9.99 = 29.97
-gadget 12 x 1,234.50 = 14,814.00
-thing 1 x 0.30 = 0.30
-title case 2 x 20.00 = 39.99
-TOTAL 14,884.26
+widget ٣ x ٩٫٩٩ = ٢٩٫٩٧ 加固版输出(9711660e):
== INVOICE ==
generated: <ts>
id: <uuid>
date: 2026 Mar 8
zone view: 12:00
widget 3 x 9.99 = 29.97
gadget 12 x 1,234.50 = 14,814.00
thing 1 x 0.30 = 0.30
title case 2 x 20.00 = 39.99
TOTAL 14,884.26
title: TITLE / info
tags: [alpha, beta, gamma, delta, epsilon]
byName: {gadget=12, thing=1, title case=2, widget=3}
cwd-relative: true
pi: 3.142
charset: <hidden>
unicode: café 日本
[基线] 与加固基线相比 0 行不同
[德语 de_DE] 与加固基线相比 0 行不同
[土耳其语 tr] 与加固基线相比 0 行不同
[美国西部时区] 与加固基线相比 0 行不同
[file.encoding=ISO-8859-1] 与加固基线相比 0 行不同
[阿拉伯语 ar] 与加固基线相比 0 行不同
加固版重复运行 12 次,与基线一致 12 次 加固版输出 17 行,引入 bug 后差异 2 行:
-TOTAL 14,884.26
+TOTAL 14,884.27
对比:不加固的输出(含时间戳 / UUID / HashSet 顺序)引入同一个 bug 之后的差异行数: 6 行(其中大部分与 bug 无关)
-generated: 2026-10-03T20:23:54.730945Z
-id: a1537cdd-7469-4e97-905d-e826272558c8
+generated: 2026-10-03T20:23:54.818913Z
+id: 43280234-e656-4c16-9ad0-7c585a806ea3
-TOTAL 14,884.26
+TOTAL 14,884.27update=false: 失败:-TOTAL 14,884.26 / +TOTAL 14,884.27 update=true: 不一致,但 update=true:已覆盖 master → 通过 再运行一次(update=false): 通过 (bug 被写进了 master) master 里现在的 TOTAL 行: ['TOTAL 14,884.27']
加固版输出里有两处值得怀疑的数字:
thing 1 x 0.30 = 0.30
title case 2 x 20.00 = 39.99
"title case" 一行显示 单价 20.00、数量 2、金额 39.99(20.00 × 2 应该是 40.00):格式化单价与金额各自做了舍入。0.1 + 0.2 显示成 0.30,原值是 0.30000000000000004。这两个现象现在都被 master 当作「正确输出」固化下来——master 只能说明输出没变,不能说明输出对。