豆备 DOUBAK
← 开发日志

页脚写着「零外部请求」,而那一版有 15 页在联网

接上老档案重新生成,样张站上 15 个页面把豆瓣的图片地址印了出来。规矩在同一份代码里写过四遍,一条测试正钉着这个 bug,而告警说了实情却没说后果。

接上 2022–2024 的老档案之后, 样张站重新生成了一遍。上一版是干净的,这一版有 15 个页面带着这个:

src="https://img3.doubanio.com/…"

而那个站点每一页的页脚都写着

整个过程零网络请求,这个页面也不发一个外部请求。

怎么来的

封面那行代码是这么写的:

douban_cover: coverPath ?? m.coverUrl
                          ↑ 本地没有这张图的时候,退回豆瓣的地址

老档案一张图都没存(前代那个工具只抓 HTML)。 于是那 5 个只存在于老档案里的作品没有本地封面,那行代码就老老实实印上了豆瓣的地址。

这个分支从来没被执行过——扩展抓的档案总是连封面一起抓下来的。 它就那么躺着,等一份没有图的档案。

三件让它更难堪的事

① 规矩在同一份代码里写过四遍

「接不回本地就只留文字,绝不回退到豆瓣的 URL」—— 这句话在生成器里写了四遍:广播里的作品链接一遍,被截断的正文一遍,广播卡片标题一遍, 还有一遍就在那行代码上面二十行, 一个叫 realCover() 的函数的注释里, 一字不差地描述了即将发生的这件事

② 一条测试正钉着这个 bug

测试文件里,一条断言要求「本地没有封面时,豆瓣的地址要被数进 remote 那一栏」—— 而紧挨着它下面那一条,解释的正是为什么不该这么做。

两条相邻的断言,结论相反。谁都没发现,因为那个分支从来没跑到过。

③ 生成器告警了,而我把它当成了通知

它是这么说的:

⚠ 有 5 张图没能从档案里取到,页面上留的是 doubanio 的地址

每个字都对。而我读完的反应是「哦,老档案没图,正常」, 然后继续做别的。它其实是在说「离线保证马上就要破了」,而它没有把这句说出来。

一条只陈述事实、不点明后果的告警,会被跳过。 这一条和校验器打完「跳过 schema 层」紧接着打「通过」 是同一个毛病。

修法:没有封面就没有封面

只写 coverPath,取不到就什么都不写。 「没有封面就只剩文字,不放占位图」这条规矩本来就已经在了。

广播的附图是刻意的例外,仍然保留远程地址:那是用户自己传的照片, 留个地址至少说明「这儿本来有张图」。现在有一条测试点名这个例外, 免得哪天它又被悄悄扩大。

顺带清掉的另外两个

一张烂掉的 JPEG,炸掉整趟生成

取图片字节那一步没有任何保护。一个损坏的 JPEG 让整个构建停在这里:

Error: incorrect data check
    at payload (src/images.js:92:15)

没有图片名,没有捕获编号,没有档案名,一个页面都没写出来。 而这条链路正好把为什么要核字节演了一遍: 解析器压根不打开图片行,所以它一路绿灯;坏消息隔着两个工具,以一句 zlib 栈回溯的形式出现。

补上保护的同时,「缺」和「读不出来」分成了两栏,因为下一步正好相反

意思该做什么
档案里就没有这张重抓一次,豆瓣还有
读不出来档案自己的字节坏了重抓没用,得找一份好的档案

合成一句「缺 N 张」,等于把人指去做一件不可能成功的事。 还有一条:解压成功之后才写盘—— 一个 0 字节的 .jpg 看起来像张图,打开是坏的,那比没有更糟。

一条 2014 年的系统广播,让 Hugo 整站构建失败

广播按月归档,而算月份的函数对着没有时间的广播返回一个字符串 '未知'下游没有任何地方认得这个哨兵。于是:

'未知'.split('-')  →  Number(undefined)  →  NaN

title: "未知年NaN月"
date:  "未知-01"      ←  Hugo: 这不是一个能解析的日期  →  整站构建中止

2026 年抓的 3411 条广播全都带时间,所以这条路一次都没被走过。 走到它的是老档案里一条 2014 年的系统广播—— 「MewX 开始收听自己的豆瓣FM」,豆瓣自己的模板生成的,确实一个时间都没有。

修法是整个字段不写,而不是编一个日期。Hugo 于是给这一页零值日期, 倒序排下来它落在最后——正是「时间未知」该在的位置。

一个下游没人接的哨兵值,和一个没人读的 verdict 是同一个形状: 它不要钱,直到第一行需要它的数据出现。

共同点

这四个 bug 里,没有一个是新写的代码引入的。 它们全都躺在那儿等一份自己没见过的数据:没有图片的档案、坏掉的字节、没有时间的广播。

而它们被发现,靠的都不是评审——是把老档案接进来,重新生成一遍,然后读产出

← 更新:名字里一个关键词都没有 更早:一个目录里十份档案,只读了一份 →