事實證明一切?
沒有 peer review 的話,有些明顯的錯誤不一定會被抓出來;但即使有 peer review,某些明顯的錯誤,還是可能會不被承認。
話說有一次開會時,我們就系統中一支程式在啟動時,會冒出一大串重複錯誤訊息的問題,進行討論。由於訊息不斷重複,可以想見是有某段啟動程式被包在迴圈裡,不停地重試(retry),當啟動總是失敗時,就會印出重複的錯誤訊息。
由於該程式經新同事改寫過,最近更因為某需求,而被改變了啟動方式,故合理懷疑是這裡出了問題。當場把程式打開來,大家一起審視。由於程式裡所呼叫的函式庫,是我撰寫的,因此我看得特別仔細,希望能從這段已經不再熟悉的程式碼中,找到一些蛛絲馬跡。
我第一眼就發現了下面這段迴圈,聞起來不是那麼好:
BOOL InitLibFoo()
{
BOOL bRet = FALSE;
while (1) {
if (Foo_Init()) {
if (Foo_Start()) {
break;
}
else {
printf("Foo_Start() failed\n");
}
}
else {
printf("Foo_Init() failed\n");
goto err_out;
}
}
bRet = TRUE;
err_out:
return bRet;
}
在這段程式裡的 while 迴圈,潛藏著一個無窮迴圈[1]:如果 Foo_Init() 成功,而 Foo_Start() 失敗,會印出 Foo_Start() failed 的錯誤訊息,然後重跑一次迴圈。由於迴圈裡並沒有做其他事,去解除這個錯誤狀態,因為環境沒有改變,在內在、外在條件不動的情況下,曾經失敗的 Foo_Start(),通常還會繼續失敗下去。因此,這個 while 迴圈,就變成了一個永遠離不開的無窮迴圈,使程式 hang 在這裡。
除非「無窮迴圈」本身就是個 feature[2],否則就是個 bug。通常來說,消極地,我們會加個 max_retry 的計數器,當重試了超過 max_retry 次時,就離開迴圈,發出 fatal error 錯誤訊息,然後結束程式,或做其他該做的事。
雖然經過更進一步分析之後發現,不斷重複的錯誤訊息,與這個迴圈沒有關係,是另一支程式的問題。不過,就算能夠假設 Foo_Start() 不會有錯誤產生[3],竊以為,這種程式邏輯的錯誤,是與 Foo_Init() 和 Foo_Start() 無關的,至少,都已經在檢查 Foo_Start() 有否成功了,當 Foo_Start() 失敗時,可能會發生什麼事,總該考慮一下吧。
基於小事應立刻處理,才不會被遺忘的原則,再加上我把自己下手修改,訂為不得不的最後選項[4],所以,我在會中提議應該修改這段程式,以避免可能的無窮迴圈。然而,某人卻強力堅持,這沒有問題,並不斷地做出保證,還說:
事實證明一切,現在程式就是沒有當,這表示程式是沒有問題的。
什麼?這句話實在是太經典了。我實在應該要記取教訓的,真正的事實,應該是因為一直沒有碰到 Foo_Start() 失敗,導致沒有機會測出無窮迴圈的問題才對吧[5]。真是好個「事實證明一切」!
後記
雖然後來進一步分析之後發現,不斷重複的錯誤訊息,與這個迴圈沒有關係,但一個禮拜後的會議裡,某人還是宣稱他解掉了這些問題,而且只加了一行!大家就很好奇,想知道這是什麼神奇的手法。原來,是把一個 Sleep(500) 放在迴圈裡,每次睡 0.5 秒[6],在「另一個」有 max_retry 保護的迴圈裡[7]。
- 英文是 endless loop。突然發覺,「無窮迴圈」這個譯法「信、雅、達」兼顧:沒有(無)窮盡的迴圈。很佩服台灣的電腦科技先進,為我們創造出了這許多的美麗譯詞。 ↩
- 例如,Windows SDK programming 裡,最為核心的那個 message dispatching loop,就是個以無窮迴圈實作的 feature。 ↩
- 後來發現,
Foo_Init()與Foo_Start()的使用,完全被搞錯了。碰到這種寫程式不看文件,原作者就在同個辦公室,卻一句話也不問的人,我實在是不曉得該怎麼辦才好。 ↩ - 不是自己寫的程式,不應不尊重地偷偷修改。講到這又想罵人了,據說上回我在修改的程式被偷偷從 workspace 裡移掉,害我程式改了老半天,就是沒有效果。又據說,這段迴圈,原本該是我負責的程式。 ↩
- 這個問題,叫做 test coverage。 ↩
Sleep(500)還可以再講一個故事。 ↩- 所以這個迴圈根本沒有動到。 ↩
Post a Comment