开发手记 · 第 4 / 9 篇 合集目录 ‹ 上一篇 下一篇 ›

读一段控制算法时,它压多少取决于三样东西:当前弯了多少、有没有压过屈服点、以及之前同一方向已经压过几次。

最后一项最让我停了一下。同一种弯,第一次压没压动,第二次它会多压一点,因为它记得上次没压动,料到这次也会回弹。这就像拧一个有点滑丝的螺丝,第一下没拧紧,第二下你会下意识多使点劲。

模型写出来是:

double press = A * (bend - tolerance) + B + C * repeatCount;

A 管弯多少压多少,B 管必须压过屈服点,C 管压不动就加量。这个设计本身我挑不出毛病。

但它有一段翻车的地方。处理压反了的分支,本意是"压反了就加力",写成了循环里拿次数自己乘自己:

for (var i = 0; i < repeatCount; i++)
    baseB = baseB * repeatCount;   // 循环里乘的是次数,不是系数

结果是 baseB = B × repeatCount^repeatCount。翻面一次还是 B,两次变成 4B,三次直接 27B,底量随翻面次数指数爆炸。而旁边明明有个专门给压反时 B 值系数准备的字段,注释都写好了,从头到尾没参与过计算。正确的应该是 baseB *= bFactor,乘那个系数,而不是拿循环变量自己累乘。

这个 bug 藏了很久才被发现,不是因为隐蔽,是因为它不报错。baseB 算飞了,程序一路照跑,没有异常,没有崩溃。现场表现就是偶尔翻面的工件压过头。这种偶发、偏工艺的现象,第一反应往往是设备或材料问题,没人会去怀疑一个累乘变量。

最让我在意的不是 bug 本身,是那个定义了却没接线的系数。它的注释写得很清楚,压反时 B 值系数,作者显然意识到了需要它,但那一行赋值之后再没被读过。一个本该是安全闸的字段,安静地躺在代码里,让所有人以为有保护,其实没有。

状态变量一旦参与迭代,每写一行都得想清楚它下一轮会变成什么。一个定义了却没被用的字段,比没有这个字段更危险,它让人以为有保护,其实没有。