我们的验证工具链,有两条规则从未生效过
一套跑了很久的静态检查工具,报告一直显示「未发现静态问题」。直到我们造了一个故意塞满缺陷的样本,才发现有两条规则从写下第一天起就从未命中过任何一行代码。这是我们的复盘,附 8 条同行可用的检查清单。
一句话结论
一个沉默失效的检查器,比没有检查器更危险 —— 它会给你虚假的安全感。规则写在文件里,不等于规则真的在跑;中间隔着一次「用缺陷样本验证它」。
那天工具的输出是这样的
[motor_control_ref.scl]
LOC=86 SLOC=61 注释率=27% McCabe≈9 嵌套=3 魔法数字=0
POU: FUNCTION_BLOCK FB200_MotorControl (line 11)
未发现静态问题怎么发现的
当时我们正在写一个测试夹具 —— 故意塞满缺陷的样本代码,目的是验证规则是否真的生效。结果出来后我们盯着屏幕看了一会儿:夹具里明明写了 #m_rB 当除数、明明有 #m_Buffer[#i_Index] 这种没有边界判断的数组访问,但规则表里那两条「除零风险」与「数组越界风险」,一条都没报。翻代码才看到原因:匹配「斜杠后面的变量名」的正则,没有考虑 SCL 局部变量带 # 前缀 —— 实际写法是 #m_rA / #m_rB,斜杠后面是空格、#、然后才是变量名,# 把匹配切断了。
这两条规则,从写下的第一天起就从来没有命中过任何一行代码。而报告,一直显示「未发现静态问题」。
同类问题:一共五种失效形态
五种形态的共同点是:它们都不会报警。工具没崩、没报错、没抛异常 —— 它只是安静地给出一个错误的结论。
第四个发现:有些缺陷,静态层原理上看不见
缺 END_IF、缺分号、括号不配对、命名违规、魔法数字、删注释 —— 静态层全部检出
AND 改 OR、删 NOT、比较符取反、改初值、删互锁 —— 静态层一条都没检出
把启动条件的 AND 改成 OR(逻辑完全反了),BLEU-4 仍接近满分
语义变异甚至让代码「更像」了参考实现。任何基于文本或结构的工具,都不可能看见它。所以我们把这条限制写进报告,而不是想办法让数字好看。
静态分析通过 ≠ 编译通过 ≠ 逻辑正确。这三件事必须分开说 —— 语义正确性只能由动态验证(仿真 / 实机轨迹比对)与人工审查负责。
我们立下的三条规矩
- 每条规则必须有「应当触发」的夹具:写完规则立刻造一个含该缺陷的样本,断言它确实报出来,并固化为回归测试
- 每条规则还必须有「不应触发」的对照:拿合规代码跑,断言零告警 —— 否则团队会因误报而集体忽略告警,那和不检查一样
- 工具本身必须先自证:45 条自检断言,跑一次几秒;规矩是自检不全绿,就不许用它去评代码
工具自检的样子
$ selftest_tools.py
断言 45 条,失败 0 条
结论:全部通过 —— 工具链按声明工作给同行的 8 条检查清单
- 每条规则都有「应当触发」的样本吗?(没有 = 你不确定它是否在跑)
- 每条规则都有「不应触发」的对照吗?(没有 = 它可能在误杀真代码)
- 告警数量级合理吗?(一个文件 17 条同类告警 = 规则过噪,团队会集体忽略)
- 工具失效时是「报错」还是「沉默」?沉默失效最危险
- 改动工具后有元测试兜底吗?(改 A 坏 B 是常态,不是例外)
- 你把哪些事明确声明为「本工具不做」?边界不写清,用户会误以为它全能
- 文档引用的每一个文件都存在吗?
- 「通过」两个字之前,有没有一行字说明它通过了检查的哪一层?
为什么愿意公开这件事
这篇文章没有增长曲线、没有 ROI。它记录的是我们自己工具链的失效与修复。之所以公开,是因为一句很朴素的话:如果一个交付方连自己的检查工具被验证过都不愿意说,那你更不该相信他交出来的东西。我们把三句话说清楚 —— 静态检查通过、能不能编译、逻辑对不对 —— 比含混地讲一句「质量有保障」,对甲方更有用。