DEV LOG · v4.1.0
踩过的坑,比做成的功能更值得写下来。
这不是变更日志——功能清单在「建设现状」页。这里记的是过程中真正卡住过的地方: 现象是什么、根因在哪、最后怎么处置的。多数坑的共同点是它们都不显眼。
建设脉络
- 起点
通读现有实现与需求文档,产出十项问题清单与一条迁移策略: 不集成、不复刻上游生产链路,先把修正数据接回来。
- 第一轮
埋点接收端与工作台。HTTP 接收 + 三层防线、登录/录入/审核/看板四个界面, 连带修掉六个存量问题。
- 补丁
处理完成埋点。补上率的分母——在此之前只能算「修正次数」, 算不出「修正率」,两条验收指标都无从判定。
- 上线
公网接入、常驻守护、联调自检脚本九条。 数据目录归零,等待真实埋点。
- 第二轮
规则回流可执行化。评估「集成或复刻上游」后改判: 规则从自由文本变成四类封闭词表的类型化配置,上游加一个加载器即可消费。
- 发布
v4.1.0。发布说明里已知限制单独成节。
- 加固
用真实模型验证提取路径,当场发现结构合法却会破坏数据的规则, 补上语义自测与调用降级。128 个测试全绿。
踩过的坑
配置文件从来没被读过
- 现象
- 示例配置文件一直在,服务启动失败时还提示「请在 .env 中设置」。
- 原因
- 没有任何代码读取它。写进去的配置全部不生效,而错误提示反过来让人以为写了就有用。
- 处置
- 补上加载逻辑,规则是真实环境变量优先、配置文件只补空缺—— 否则线上用环境变量设的值会被仓库里的文件悄悄覆盖。
隧道地址拿到了,请求全部失败
- 现象
- 公网隧道建立成功、地址也拿到了,但所有请求返回 530。
- 原因
- 出网环境封了 UDP 7844,而隧道客户端默认走 QUIC,握手一直超时。 它的启动预检其实已经打印了「UDP 不通、TCP 通」,只是淹没在日志里。
- 处置
- 强制走 HTTP/2 回退,立即注册成功。这条写进了部署文档—— 换台机器大概率还会遇到。
边缘防护悄悄吃掉埋点
- 现象
- 换到正式域名后,自检九条全部 403, 但用 curl 手工发同一个请求却是 200。
- 原因
- CDN 的机器人防护按 User-Agent 拦截。实测 12 种主流 HTTP 客户端, 只有一种被拦——恰好是自检脚本用的那种。
- 处置
- 脚本改发具名 UA,并在全 403 时直接输出「这是边缘拦截」的判断。 真正危险的是:被边缘拦掉的请求根本到不了服务端, 兜底队列救不了、日志里也看不见——表现为「上游以为发了,我们这边什么都没有」, 正是数据不可追补的那种丢法。
图上的箭头全丢了,截图看不出来
- 现象
- 一张流程图渲染「正常」,截图检查也没发现问题。
- 原因
- 共享样式表里有一条针对图形元素的通用规则, CSS 会覆盖 SVG 的表现属性——箭头标记被换成了一个不存在的引用, 警示用的语义色也被刷成了普通灰。图的核心信息(哪两段还没通)视觉上完全消失。
- 处置
- 改用不设子元素样式的独立容器,并把验证方式从「看截图」换成 读取计算后样式逐项核对。后来画大幅架构图时, 又用脚本量了全部 68 个文字元素的包围盒做重叠检测——零碰撞。
差点为一个假想的消费者做抽象
- 现象
- 注意到姊妹项目与本项目「结构同构」,一度把「抽出共用模块」 列为当前最该做的技术投入。
- 原因
- 那句「结构同构」来自代码注释,没有人去量实际重叠。 实测后发现两边根本没有重复代码:一边 106 行含类型化契约与兜底队列, 另一边 33 行写自由字典;规则相关的代码,姊妹项目一行都还没写。
- 处置
- 否决自己的建议。为一个真实消费者加一个还没动工的假想消费者做抽象, 接口大概率要推翻重来。结论是「现在不做」,并把证据写下来。
规则通过了校验,却会破坏数据
- 现象
- 类型化提取的模型调用路径此前从未被真实验证过——测试里它是被替身覆盖的。 实测后发现:某条归一化规则通过了全部结构校验,参数键合法、正则能编译。
- 原因
- 它用了另一种语言的反向引用语法,在本运行时会被当成字面量插入—— 把「2023年」变成「$1年」。拿它自己的六条证据样本回算,0/6 成立。 结构合法不等于语义正确:schema 只能挡住形状不对的参数。 更危险的是这种规则会一路走到人工审核,而审核人看到「像模像样的正则」很可能就批了。
- 处置
- 用设计里已有的东西:规则强制附带的证据样本,就是它自己的测试用例。 入库前把规则应用到每条样本上回算。判据分三态——改对、没改(正则未匹配,原样返回,无害)、 改坏(一票否决)——再加覆盖率下限。 真实模型复测:三轮中一轮放行,改对 4、未覆盖 2、改坏 0。 未覆盖的样本会继续产生修正记录,攒够了自然提出配套规则,一次解决一部分。
几条被反复验证的做法
测试先红后绿,而且要验证它真的会红
每个功能都先写会失败的测试。更进一步:写完防回归测试后, 把旧写法塞回去确认它确实变红——否则你可能只是加了一条永远为真的断言。
把约定变成会失败的测试
文档里写「改了要同步」,没人会记得。有一条测试盯着文档里写死的测试数量, 七次改动触发了七次拦截——包括一次跨分支合并时自动发现文档过期。 约定靠自觉会漂移,靠测试不会。
验证要量化,不要靠眼睛
截图「看着没问题」骗过一次之后,后续的图形验证全部改成读计算样式、 量包围盒、算重叠。同理,部署完不只看「进程在跑」—— 直接杀掉进程,确认它会自己回来,那才叫守护。
结构化的东西可以校验,自由文本不能
规则从自然语言改成带 schema 的参数之后,模型编造出一个不存在的数据源名, 入库时当场被拒。写文档时作者本人就编过一个这样的源名,是自查脚本抓出来的。 能被机器拒绝的错误,就不该留给人去发现。