有一类问题,特别容易把人带进错误的方向:渲染器已经升级,产物看起来也像是新版本,但验收门禁连续报错。报错一多,人的第一反应往往是怀疑这次改动,是不是字号变了、坐标偏了,或者某个字段没有写进去。于是,排查很快变成了反复改产物、反复重跑门禁。
这次遇到的情况却更值得停下来想一层。新产物失败,历史产物也失败;样式校验里保存的字号和坐标,又已经和渲染器实际使用的值脱节。另一个看似无关的报错,是作者字段被识别成了缺失,后来才发现文字识别把简体字读成了繁体字。
表面上,这是一次验收失败。实际上,我要判断的是:失败究竟来自生产结果,还是来自验收者手里的旧尺子?如果这个问题没有先分清,后面每一次修补都可能把真实问题再遮深一点。
先确认失败是不是新问题
我现在遇到持续失败的门禁,第一步不会马上改规则,也不会立刻回到渲染器里调参。我会先拿旧样本跑一遍,确认历史产物是否也会触发同样的失败。这个动作很朴素,却能把排查从“这次发布是不是坏了”,转成“哪一层的判断一直不对”。
如果新旧产物都失败,说明问题未必是这轮改动带来的回归。旧样本本来已经在过去的流程里存在,今天仍然被同样的规则拦下,至少说明门禁与真实生产之间可能早已有了偏差。新版本只是让这个偏差变得更明显,并不自动等于新版本制造了它。
历史产物也失败时,先怀疑基线和验收口径,而不是先给新改动定罪。
这个判断很重要,因为它会改变排查顺序。若只测试新样本,我可能会继续寻找渲染器里的局部错误;加入旧样本后,才有机会发现失败是稳定复现的。稳定复现不是坏消息,它反而把范围缩小了:需要查的不是某一张图、某一段文字,而是生产和验收之间长期没有同步的约定。
如果不处理这一步,后果是很具体的。团队会把时间花在改一个已经符合生产要求的产物上,甚至为了让当前回归变绿而回退渲染器。最后得到的不是更可靠的系统,而是一套被旧基线牵着走的生产流程。
把旧尺子和生产真源放在一起看
确认新旧样本都失败之后,下一步是逐项对照验收脚本里的样式常量和渲染器实际使用的字号、坐标。这里不能只看报错文字,也不能只看最终图片“肉眼像不像”。要把门禁拿来检查的期望值,和真正负责生成产物的值放在同一张对照表里。
这次最关键的发现,就是验收脚本仍然保存着旧字号和旧坐标,而渲染器已经改版。于是,脚本不是在判断“产物是否符合当前生产规则”,而是在判断“产物是否符合过去某个时刻的规则”。只要两处值各自维护,升级后就很容易出现这种漂移。
这里最容易误判的地方,是把“常量还在脚本里”理解成“规则还有效”。常量存在只能说明有人曾经把一个判断写进去,不能证明它仍然来自生产事实。验收值如果没有可追溯的来源,时间一长就会从规则变成遗留物。
门禁不是另一套生产规范,它应该从生产真源派生出自己的期望值。
我选择先找生产真源,是因为只有这样才能回答“当前应该是多少”。这个真源可以是渲染器共享的常量,也可以是产物生成时留下的结构化回执。前者减少运行时的解释空间,后者更适合需要对最终产物负责的流程。无论选哪一种,关键都不是技术形式,而是不要让验收人员再手抄一份数字。
别把识别差异当成字段缺失
样式漂移之外,还有一类更隐蔽的误报来自文字识别。作者字段明明出现在产物里,门禁却报告缺失。继续检查原文和图片后,才发现识别结果只是把简体字写成了繁体字。字形不同,语义却没有变;如果验收只做逐字相等比较,就会把“识别差异”错判成“内容不存在”。
这并不意味着所有相似文字都应该放行。文字等价必须有边界,否则一个真正写错的作者名,也可能被模糊匹配吞掉。我的处理方式是先做有限的归一化,只覆盖明确的简繁对应,再保留字段存在性、长度和关键内容等其他约束。这样,门禁承认识别工具的已知偏差,却不因此失去判断能力。
我没有选择大范围模糊匹配,是因为验收本来就承担着拦截责任。识别结果和预期之间存在差异时,应该问“这是允许的表示变化,还是内容变化”,而不是简单地把比较放宽。只有能解释、能枚举、能回归的等价关系,才适合进入规则。
这件事让我重新意识到,自动化验收面对的不是纯净数据。渲染、压缩和文字识别都会带来边界误差。可靠的规则不是假装这些误差不存在,而是把可接受的误差写成有限范围,同时让越界情况继续失败。
错误捷径会让门禁失去意义
最容易想到的错误捷径,是为了让回归变绿,直接降低阈值、删除失败项,或者把门禁改成只记录不拦截。它们都能迅速减少红色结果,却没有回答真正的问题:产物是否仍然符合生产约定?
另一个隐蔽的捷径,是只修验收脚本,让它迁就当前几张样本;或者只修渲染器,让它重新符合那份过期的旧常量。前者会让门禁越来越松,后者会让生产被历史基线锁住。两种做法看起来方向相反,本质上都是绕开了真源。
让回归变绿不是目标,证明门禁仍能拦住错误才是目标。
如果规则只对正确样本给出通过,而没有设计故意错误的产物,任何“修复”都可能只是把检查变成装饰。门禁真正有价值的时刻,不是它没有报错,而是它能在一张越界产物出现时明确报错,并且报的是能帮助定位的原因。
所以我判断,错误捷径必须从一开始就排除。阈值要不要调整、文字差异要不要归一化,都应该回到生产事实和可解释的边界上,而不是回到“怎样最快得到绿色结果”上。
用双向样例证明规则没有变软
修正真源关系和有限文字归一化之后,我会用两类样例做回归。第一类是已知正确的真实产物,应该能通过样式、字段和内容检查。它验证的是规则没有继续把合格结果误拦下来,也验证了渲染器和验收现在使用的是同一套期望值。
第二类是故意制造的错误产物。可以让字号越界、坐标偏移,或者删掉关键字段,再确认门禁仍然失败。对于文字识别,还要分别覆盖允许的简繁差异和真正不相等的内容,证明归一化只解决已知误报,没有把比较变成无条件放行。
这两类样例缺一不可。只有正例,无法证明拦截能力仍在;只有负例,又无法证明新规则没有误伤正常生产。更稳妥的做法,是把它们作为长期回归样本保存下来,而不是修完一次就丢掉。
一个门禁是否可靠,要同时看它能让正确产物通过,也能让故意错误的产物停下来。
我会把每个样例对应的预期结果写清楚:它验证的是哪条规则,为什么应该通过或失败,失败时应当出现什么类别的提示。这样下一次渲染器再升级,排查就不必从“全红”开始猜,而可以沿着样例快速判断是生产真源变化、允许差异变化,还是门禁实现出了问题。
下次我会怎么做
下次再遇到渲染器升级后验收持续失败,我会按固定流程走。先用旧样本和新样本分别复现,判断失败是否跨越版本;再把门禁期望值与生产侧的实际字号、坐标和字段回执逐项对照,确认当前真正的真源;接着只对已经解释清楚的识别差异做有限归一化,保留其他严格检查;最后用正确样例和故意越界样例双向回归。
我会特别记录每次规则变更的理由。改了哪一个期望值,它来自哪个生产事实;加入哪一种文字等价,它可能误放过什么;负例为什么仍然应该失败。这些说明不是附属文档,它们帮助下一位排查者区分“规则变了”和“规则坏了”。
一个流程成熟的标志,不是它能让所有样本都通过,而是它能解释为什么通过、为什么失败。表面上我是在修一套验收脚本,其实是在修生产和判断之间的信任关系。只要尺子从真源派生,边界被明确留下,门禁就不再只是制造红色提示,而会成为升级时帮助我们少走弯路的安全网。