每行代码都有潜在的 bug
去年夏天我写了一些代码来实现从一个哈希表中获取一条消息。这条消息是将要通过另外一个线程放入哈希表中的。这里会有很小的概率发生冲突,即一开始查找消息的时候它还没有被保存进去。查找的代码如下:
while ((message = map.get(key)) == null && System.currentTimeMillis() < timeoutTime) { wait(1000); }
wait() 函数调用阻塞当前线程,等待负责向表存入消息的线程调用 notifyAll() 函数。这里 1000 表示 1 秒。大约 5 秒后将会超时。
上面的代码是简单而且正确的。它将会一直保持循环,直到获取得到数值或者超时。超时最多延迟1秒,但在这个案例中不会有问题。(或者说,直到超时发生,你才会遇到更多严重的问题)
代码被另外两个人评审。两个人都抱怨 wait() 函数需要等待”当前”到超时之间的整个时长,而不是仅仅 1 秒。他们认为我的代码不必要地唤醒了线程五次。我的回复是只有在第一秒内是最有可能读取到消息的,而且唤醒一个线程的代价并不大。我认为他们提出的代码会太复杂,而且更有可能存在bug。
他们都说,“一个减法并不复杂”,然后就回到了自己的座位上将他们修改后的版本通过邮件发送给我,想要以此证明他们的办法是多么简单。两个人的代码分别都出现了一个 bug。第一个人的 bug很简单:他使用了错误的常量进行计算。但第二个人的bug却非常微妙:
while ((message = map.get(key)) == null && System.currentTimeMillis() < timeoutTime) { wait(timeoutTime - System.currentTimeMillis()); }
这里会存在很小的可能性使得在做减法时,当前时间超过了超时的阈值,从而产生一个负值并传递给了wait(),进而会抛出一个IllegalArgumentException的异常。为了省得计算机一次罕见的线程切换,他引入了一个会偶尔发生并将不可思议地导致运算失败的bug。
(2010年3月15日更新:Ajit Mandalay指出另外一个不好的细节:减法得到0,这意味着“无穷”,循环就有可能永远不会退出)
你写的每一行代码都可能会有一个潜在的bug。所以,除非是当前立刻就需要的或者程序缺了就不能正常运行的,请不要写任何代码。不要推测性地写例程。如果不是立刻需要,就不要写抽象层。如果一个优化会增加任何的复杂性,哪怕是一个减法,也请抵制它。否则五年后,当你的代码中充满有可能是错误的而又从未真正需要的代码时,你会非常遗憾后悔的。
本文文字及图片出自 伯乐在线
你也许感兴趣的:
- 我是如何在第一款登月游戏中发现一个 55 年前的漏洞的
- 【外评】航空公司总是把 101 岁的老太太误认为婴儿
- 【译文】经常嗡嗡叫的虫子(bug)
- 【程序员搞笑图片】要不要上报?
- 【译文】满月时,代码工作异常
- 【译文】bug 经济学
- 【译文】一行代码如何造成 6000 万美元的损失
- 我所见过的最奇怪的Bug
- Google在一个函数中放入2万个变量,引发Firefox大崩溃
- 离职两年后,程序员遭前东家索赔:Bug 是你写的
还是作者同事的写法更好,只是需要先把当前时间保存起来,供wait方法调用。这样就不会使得wait被调用时又一次读取新的当前时间,从而导致负数或0。
long curTimeMillis = System.currentTimeMillis();
while ((message = map.get(key)) == null && curTimeMillis < timeoutTime) {
wait(timeoutTime – curTimeMillis);
}