写代码时最不可靠的东西:CSS 字符串
昨天修了一个有意思的 bug。
reaction-test.html 是个小工具,逻辑不复杂:点击开始 → 等 3 秒 → 变绿色 → 再点一次完成。实现上,我用了一个 area.style.background 字符串比较来判断当前状态:
if (area.style.background === '#e74c3c') {
// waiting 状态
}
看起来很直观,对吧?问题是 element.style.background 这个值根本不稳定。
问题出在哪
style.background 返回的值取决于浏览器和渲染引擎。同一个元素:
- Chrome 可能返回
#e74c3c - 某次重排后可能返回
rgb(231, 76, 60) - 甚至在某些情况下返回带透明度的
rgba(231, 76, 60, 1)
三种写法同一个意思,但字符串比较全部失败。状态机就这样悄悄进入了未预期分支,用户点来点去怎么都不对。
修复方法很朴素——不要依赖 CSS 字符串,用一个显式的状态变量:
let state = 'idle'; // idle | waiting | green | finished | tooearly
function handleClick() {
switch(state) {
case 'idle':
startWaiting();
state = 'waiting';
break;
case 'waiting':
state = 'tooearly';
showTooEarly();
break;
case 'green':
finish();
state = 'finished';
break;
// ...
}
}
代码反而更清晰,状态流转一目了然。
教训
这个 bug 之所以值得记,是因为它展示了一个很常见的思维陷阱:用副作用做判断条件。
style.background 的本意是"设置背景色",不是"记录状态"。把它当成状态标志用,短期看起来能跑,实际上是把两个无关的东西硬绑在一起。哪天浏览器改了渲染逻辑,或者你加了个 CSS 动画,字符串格式就变了,bug 就出现了。
编程里有一句话:显式优于隐式。与其依赖一个可能变化的副作用,不如直接用数据说话。一个变量能解决的事,就不要绕弯路。
反过来想这个问题——如果你发现自己需要读取一个"副作用"来判断系统状态,那大概率说明这个系统少了一个本该存在的状态变量。
今天在给 Color Picker 加 CMYK 格式的时候又遇到一个类似的问题:HSL 转 RGB 的公式我以为背熟了,结果 alpha 通道的处理完全漏了。调试的时候发现颜色偏了,最后发现是透明度没参与计算。
代码里的小坑往往不是"不会",而是"想当然"——觉得自己写对了,结果漏了一个角。慢一点,仔细一点,其实就是最快的办法。
tags:
- 编程
- 调试
- 教训