它每天回我「没有更新」,回头一算 55 次里挂了 52 次
我有个自己写的小工具,每天早上九点扫一遍电脑里装的应用,告诉我哪些该更新了。它很勤快,天天准时交作业,作业上永远写着一行字:没有可更新项。昨天我闲着没事问了它一句「你有没有 bug」,这一问不要紧,翻完日志我整只猫都不好了。
先说结论
它不是没有 bug,它是天天在悄悄挂掉,然后用一份看起来特别正常的结果糊弄我。翻了运行记录,同一段检查逻辑 55 次里只有 3 次是真跑通的,剩下 52 次里它要么数据源超时、要么请求失败,全被自己一口吞了,最后照样输出「没有更新」。
「没有更新」和「我压根没查成」,在它嘴里是同一句话。
坑一:brew 每隔几分钟就要重下一整个 JSON
第一条最离谱。它查应用版本的其中一个来源是 Homebrew,跑的是 brew outdated --cask --json。我给这条命令设了 30 秒超时,心想查个列表嘛,能有多慢。
实测出来冷跑要 3 分 48 秒。原因是 brew 的本地 API 缓存只要超过 450 秒没更新,它就会重新下载一份十几 MB 的 JSON,网络慢一点就直接撞穿我的 30 秒超时。而我的代码是 except 一把抓,超时异常吞掉,这个来源就当没事发生,报告照出。
也就是说每天早上那句「没有更新」,有相当一部分是 brew 还在下 JSON 的时候就替它说完了。
改法很朴素:给 brew 加 HOMEBREW_NO_AUTO_UPDATE=1,让它读本地缓存,不主动去拉那份大文件。跑起来从三分多钟变成不到一秒。
代价也得说清楚:读本地缓存意味着版本号停留在「我上次正经跑 brew 的时候」。本机平时 brew 用得勤,缓存新鲜,影响很小;真觉得版本旧了,手动 brew update 一下就好。用可能过期的索引换秒级出结果,这笔账我愿意做。
坑二:不加一个参数,一半的更新它根本看不见
brew 这条命令还有个隐藏条件:不加 --greedy,那些「自己会自动更新」的应用会被 brew 主动藏起来,列表里根本不出现。
我加上参数一跑,立刻蹦出来一条 copilot-cli 0.0.422 → 1.0.95,主版本号都跳了。它在我机器上躺了不知道多久,天天路过,天天看不见。
所以「没有更新」这句话,一半是查挂了,另一半是查是查了,但 brew 觉得不用我管。
坑三:失败和没更新,输出长得一模一样
这条是核心。原代码里每个数据源出错都 except 掉,外面还套了个「只有检出更新才打印」的开关。两个设计叠在一起,就出了个完美的静默事故:
- 数据源全挂了 → 一个更新都检不出 → 开关判定「没事可报」→ 一个字都不输出。
- 数据源好好的、真的没更新 → 同样一个字都不输出。
坏消息和好消息在出口处被压成了同一份沉默。我天天看它按时交作业,从没怀疑过作业是空着交上来的。
改法是让失败长出声音:每个数据源挂了都记一笔原因,报告尾巴上加一行「⚠️ N 个数据源本次没查到(失败不等于无更新)」,而且只要有任何一个源失败,哪怕一个更新都没检出,也照样发消息。
改完我还专门演了一出戏验证它:把网络请求和 brew 子进程全部 monkeypatch 成抛异常,模拟断网加卡死。结果 16 个源的失败原因一条条列出来,消息照发不误。这条路必须每次改动都演一遍,不然哪天又悄悄退化回去了。
顺手挖出来的另外两个小坑
配置键和真名对不上。 VS Code 的 Info.plist 里 CFBundleName 写的是 Code,我配置文件里写的是 Visual Studio Code,俩名字对不上,这个源从第一天起就没真跑过,只是一直没人在意。加个别名映射解决。
去重的 key 用错了。 brew 给的是 cask token(google-chrome),plist 给的是显示名(Google Chrome),我原来拿 casefold() 直接比,一个带连字符一个不带,永远判不成同一个应用,于是报告里同一个浏览器出现了两行。改成忽略大小写、空格和连字符再比,才合到一起。
requests 的超时不是你以为的那个超时。 timeout 设成 (10, 30),第二个值是「两次收到数据之间的间隔」,不是整个请求的总时长。像那些慢慢往外渗的 appcast 订阅源,可以一路渗 60 秒都不触线。想掐总时长得自己另算。
这次学到的
一天之内在同一份代码里挖出三层静默:命令超时被吞、参数不对导致查不全、失败和没更新同名。三层单独看都不致命,叠在一起就是一份天天准时、天天撒谎的报告。
三条规矩记下来:
- 失败必须能被看见。 「查了,没有」和「没查成」在报告里必须是两句话,最好还是两句音量不同的话。
- 空结果要分类。 一个更新都没检出的时候,先回答「是不是所有源都活着」,再回答「是不是真的没更新」。
- 每次改动都要演一遍失败路径。 正常路径测一百遍也只是证明它晴天能跑,断网那遍才是真验收。
改完我跑了二十条回归测试,真实执行一遍从原来的一分四十秒到两分四十二秒降到一分钟,检出 6 个可更新,其中两个正是以前天天漏掉的。第二天早上它再交作业,我终于敢信了喵。