一个不报错、但用户一眼能看出的错
今天收两条用户反馈:宝可梦猜谜小程序里,特性"款待"的线索卡写的是"面影辉映"的文案;图鉴里美录梅塔被标成"第 1 阶段"(明明是进化形,应该是第 2 阶段)。两者都不崩、不报错、不影响答题,但数据正确性被破坏了。做了十年前端,我越来越怕这种"看起来都对、其实错了"的脏数据——它不会在测试环境炸出来,只会在用户眼前慢慢露馅。
根因一:对齐键用了会漂移的代理键
"款待"串位的锅,出在取数方式上。特性说明来自官方中文 dump,我图省事按数字 id 取:
// 旧:按数字 id 取,PokeAPI 与官方 dump 编号在九代尾部错位
const text = OFFICIAL[`TOKUSEIINFO_${localId}`]
// 新:按官方中文名对齐,名字是唯一稳定锚点
const text = nameToEffect.get(officialName)
本地宝可梦编号来自 PokeAPI,两套编号在第九世代尾部对不上:款待本地是 301,官方 dump 里是 299。半年前那次全量刷新按 id 覆盖,把 299~311 整段错位,一共 11 条错。招式池因为两边编号一致,反而没事。
修法很朴素:把对齐键从"会漂移的数字 id"换成"唯一稳定的官方中文名",重建"中文名 → 说明"映射按名字对齐;同名多 id 的变体合并;再加一条"串位"判据——如果某条描述恰好是池子里另一条官方特性的文本,就判它坏值重取,默认模式自愈,不用再手动跑全量刷新。同时补了 --dry(只打印要改什么)和 --all(强制全量重取)两个开关,方便复查。
根因二:快照会过期
美录梅塔那条更隐蔽。它的进化链数据是从 PokeAPI 抓的 evolution-chain 快照,当时美录坦、美录梅塔各是独立链条;现在官方把两条合并成一条(美录坦 → 美录梅塔),快照就 stale 了。全量对账发现结构差异只此一处,但"阶段"这个字段散落在 9 个文件加后端 4 份副本里。
更要命的是个生成脚本的形状陷阱:gen_dex_ext.js 原本给 typeQuiz 的对比板数据写成瘦形状(没有 bst/stage),而 typeQuiz 的对比板要读这两个字段——一跑就把阶段列全变成 1、bst 比较变成 undefined。修法是让 typeQuiz 复制 dailyRiddle 的字段形状,statsQuiz 保持瘦形状(bst 现算、stage 读 stage_map),并在注释里写清两份形状故意不同。
最难的不是找根因,是"数据有多个真相源"
这两个 bug 暴露的共性问题,是同一份数据存在多处真相源:特性文案同时存在于前端包内、云函数、自建后端,改一处要同步三处;阶段数据更有 9 个落点。任何一处漏改,都会产生"半对"状态,而且测试很难覆盖到。
自查的坑:基准选错,比对全绿
修完做自查时我踩了另一个坑:第一遍拿 git show HEAD 当"改动前基准"比对,结果全绿——因为改动早就 commit 过了,HEAD 就是新版。正确基准应该是改动前的那个 commit(d8b5806^)。这个错误正好掩盖了一个真实遗漏:百科首页卡片"541 个家族"漏改 540,而且只改在工作区、没提交。一旦发版,就等于"用户拿到的版本"和"仓库记录的版本"对不上,换机器就丢、也没法追溯。
兜住它:把一致性当契约来测
结论是,这类问题靠人肉比对必漏。我给这条数据管线配了四套审计脚本当发版门禁:
audit_pools.js:特性池三份副本(data / 云函数 / 后端)逐字一致;audit_stats_dual.js:包内 stage 与后端 statsQuiz/stage_map 对照;test_kb.js:图鉴/百科断言(含阶段数字这种写死的统计);test_pokedex.js+ 后端audit:puzzle:图鉴全量与题库双跑。
发版前这四套全绿,才敢放行。两个更通用的经验:对齐键永远选"稳定的唯一标识"(名字)而非"易漂移的代理键"(id);"抓一次存下来"不等于"永远对",快照要有再对账的机制。
部署上,后端 npm run build && pm2 restart 即时生效;小程序端随下次发版带上,池长、顺序、id 全不变,每日答案与历史记录零影响。
做前端久了,我越来越相信一句话:功能写错顶多白屏,数据写错才会悄悄吃掉用户的信任。把数据当契约来测,比把它当文本来改,要稳得多。