每行程式碼都有潛在的 bug

樂徒發表於2015-05-08

去年夏天我寫了一些程式碼來實現從一個雜湊表中獲取一條訊息。這條訊息是將要通過另外一個執行緒放入雜湊表中的。這裡會有很小的概率發生衝突,即一開始查詢訊息的時候它還沒有被儲存進去。查詢的程式碼如下:

wait() 函式呼叫阻塞當前執行緒,等待負責向表存入訊息的執行緒呼叫 notifyAll() 函式。這裡 1000 表示 1 秒。大約 5 秒後將會超時。

上面的程式碼是簡單而且正確的。它將會一直保持迴圈,直到獲取得到數值或者超時。超時最多延遲1秒,但在這個案例中不會有問題。(或者說,直到超時發生,你才會遇到更多嚴重的問題)

程式碼被另外兩個人評審。兩個人都抱怨 wait() 函式需要等待”當前”到超時之間的整個時長,而不是僅僅 1 秒。他們認為我的程式碼不必要地喚醒了執行緒五次。我的回覆是隻有在第一秒內是最有可能讀取到訊息的,而且喚醒一個執行緒的代價並不大。我認為他們提出的程式碼會太複雜,而且更有可能存在bug。

他們都說,“一個減法並不複雜”,然後就回到了自己的座位上將他們修改後的版本通過郵件傳送給我,想要以此證明他們的辦法是多麼簡單。兩個人的程式碼分別都出現了一個 bug。第一個人的 bug很簡單:他使用了錯誤的常量進行計算。但第二個人的bug卻非常微妙:

這裡會存在很小的可能性使得在做減法時,當前時間超過了超時的閾值,從而產生一個負值並傳遞給了wait(),進而會丟擲一個IllegalArgumentException的異常。為了省得計算機一次罕見的執行緒切換,他引入了一個會偶爾發生並將不可思議地導致運算失敗的bug。

(2010年3月15日更新:Ajit Mandalay指出另外一個不好的細節:減法得到0,這意味著“無窮”,迴圈就有可能永遠不會退出)

你寫的每一行程式碼都可能會有一個潛在的bug。所以,除非是當前立刻就需要的或者程式缺了就不能正常執行的,請不要寫任何程式碼。不要推測性地寫例程。如果不是立刻需要,就不要寫抽象層。如果一個優化會增加任何的複雜性,哪怕是一個減法,也請抵制它。否則五年後,當你的程式碼中充滿有可能是錯誤的而又從未真正需要的程式碼時,你會非常遺憾後悔的。

相關文章