沒有 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]


  1. 英文是 endless loop。突然發覺,「無窮迴圈」這個譯法「信、雅、達」兼顧:沒有(無)窮盡的迴圈。很佩服台灣的電腦科技先進,為我們創造出了這許多的美麗譯詞。
  2. 例如,Windows SDK programming 裡,最為核心的那個 message dispatching loop,就是個以無窮迴圈實作的 feature。
  3. 後來發現,Foo_Init()Foo_Start() 的使用,完全被搞錯了。碰到這種寫程式不看文件,原作者就在同個辦公室,卻一句話也不問的人,我實在是不曉得該怎麼辦才好。
  4. 不是自己寫的程式,不應不尊重地偷偷修改。講到這又想罵人了,據說上回我在修改的程式被偷偷從 workspace 裡移掉,害我程式改了老半天,就是沒有效果。又據說,這段迴圈,原本該是我負責的程式。
  5. 這個問題,叫做 test coverage。
  6. Sleep(500) 還可以再講一個故事。
  7. 所以這個迴圈根本沒有動到。