2015年6月8日 星期一

debug 的奧秘(1)

這裡不會討論 gdb 之類的東西,因為無論是網路上還是市售書籍可說是汗牛充棟,也不需要多一篇來浪費各位的頻寬,要講當然就要講別的地方比較少談到的議題。

總訣篇


不知道各位有沒有想過 debug 除了那一堆零碎的技巧與工具外,有沒有什麼內功心法?有沒有什麼共通的準則讓我們在碰到新問題時不至於手拙無措?

如果您有這樣想過,您的水平已經高過一般工程師,已經往哲學思考的層次邁進

不過,這種內功心法不要說學校不會教,就連現實生活中也絕少有人提及。

站長在這方面還沒有資格當各位的老師,不過倒是可以推薦兩本武功秘笈給大家回去修煉

調試九法

這是一本薄薄的小書,裡面列舉了九條除錯的法則還有相關小故事,這九條分別是:

  1. 理解系統
  2. 製造失敗
  3. 不要想,而要看
  4. 分而治之
  5. 一次只改一個地方
  6. 保持審計跟蹤
  7. 檢查插頭
  8. 獲得全新觀點
  9. 如果你不修復 bug,他依然存在
這九條規則有些看起來簡直就像廢話,但正所謂大道至簡,沒有在這行業打滾十幾年是無法究其義的。下面讓站長跟大家分享一下自己的觀點...^_^

1. 理解系統

其實就是 RTFM(Read The Fucking Manual),大多數人常常稀哩糊塗的就從 Google、Stack Overflow 抄答案交差了事,站長也常幹這種事。但是事後有多少人知道自己改了什麼?對系統有什麼影響?

又好比寫 firmware 時,懶得看厚厚幾百頁的 datasheet,而是用 PDF Reader 直接來個全域搜尋,有多少人會一頁一頁從頭仔細看呢?切記

你不知道的部份可能對你有害

2. 製造失敗

看到這條先別口吐白沫。很多人一定會想說,就是複製 bug 嘛,這我天天幹,誰不會啊 !#$@^&...

但是有一件事是很少人會做的,就是當你認為你解決問題之後,有沒有試著回到未解決之前的狀態,再次確認 bug 同樣會發生?從邏輯上來看,就是要確認以下的關係:

P<->Q
而不是:
P->Q
要避免後者的原因很簡單,就是發生 bug 的條件有多種,而你只解決一種,而這也證明第一條「理解系統」有多重要。

3. 不要想,而要看

要努力克制「那個問題不可能發生」的念頭,而是要仔細的觀察每一筆輸出,log 上的每一筆紀錄,還記得福爾摩斯的名言嗎?

排除了一切不可能的因素之後,剩下來的東西儘管多麼不可能,也必定是真實的

不過有時插入這些 debug code 之後反而系統就正常了,未來會再另外討論這個問題。

4. 分而治之

其實就是要好好利用二分搜尋法(binary search)。舉一個例子,Linux kernel 編譯完後會留下一個 system.map,裡面會記載 kernel 所有變數、函式等名稱與記憶體位址的對應,而系統發生 Oops 崩潰時會 dump 所有暫存器的內容,此時就可以利用 PC(Program Counter) 值對 system.map 進行二分搜尋,這樣很容易就能找到 bug 位於哪個變數或函式區間。

5. 一次只改一個地方

在時間壓力下,工程師往往免不了會想多修改幾個看似有問題的地方,趕快把問題解決。但除非真的對系統非常了解,否則往往落入事倍功半的情境。

攀岩有一句口訣可以說明這個精神:「三點不動一點動」

除非你確認已經抓穩下一點,否則這一躍可能是粉身碎骨。

6. 保持審計跟蹤

這可以用站長最近的一則小故事說明,上星期測試部門表示站長開發的新功能引起系統崩潰,站長回到座位上 clone soruce code,編譯測試發現一切正常,馬上喜孜孜的衝去跟同事說很正常沒問題啊,經同事提醒下才發現自己的測試方式與他們略有差異,再回去測試馬上找到問題。

這代表發生問題的當下紀錄問題有多麼重要,我們常常會在電影中看到科學家用攝影機、錄音棒紀錄正是如此,因為人的記憶力與注意力是如此靠不住。

另外一方面,這也代表了現場主義的重要性,因為你絕難把客戶現場的環境 100% 複製回辦公室,試著拎起你的工具箱出門吧,這一切會值得的!

7. 檢查插頭

大部分人查找問題常常會有一個先入為主的觀點「問題一定是出自於那個貌似最不穩定的元件」

這個不穩定的元件可能是新手寫的程式、未認證的電子零件、新廠商的產品...debug 時必須拋棄這種「長得像壞人一定是嫌疑犯」的觀點,而是要用客觀的角度看待系統的每個組成部分,真相大白之前,人人都有可能是嫌疑犯!

8. 獲得全新觀點


當 1-7 無法奏效該怎麼辦?其中一種解決方法是,試著把自己的問題解釋給別人聽,這人不必然是這方面的專家,傳說微軟甚至要求員工先解釋給辦公室的布娃娃聽,這是為什麼呢?因為往往這時後會發現,我們對問題描述的不夠清楚,也就是說你連發問的資格都沒有

大家景仰的李家同伯伯也強調過「大量閱讀的重要性」,人為什麼要大量閱讀?其中很重要的地方就是要獲得「不同觀點」的能力,比方說我們可以用歷史學的孤證不立來檢驗問題,用控制論中不同通道傳遞訊息的觀點來看問題,往往在一個領域看似很困難的問題,在其他領域可以找到好的類比甚至答案。

9. 如果你不修復 bug,他依然存在

站長一直跟很多人強調一個觀念是:

修復 bug 優先於開發新功能

忽視問題只會讓問題如滾雪球般越滾越大,就算是單純的忽視 Compiler warning,也往往會造成重大災情,例如 mingw 可以容忍 function 沒有 return value,假如今天這個 function return value 被用於 array index,這樣是不是產生一個不定時炸彈?

又如站長最近花了很久才解決的問題,這個問題的現象是在 IDE debug mode 時正常,但 runtime 時就崩潰了,找了很久才發現在 IDE debug mode 時會把 class member variable 初始化為 0,而 runtime 時並不會,很明顯的這是沒有好好的「檢查插頭」。

 在「bug 與考績」中提到了過去站長被主管要求 bug 不能太多,其實還忘了提到一件事,就是程式不能有任何 warning,否則就要提出解釋為什麼會有這些 warning。到後來,站長乾脆在測試時就先啟動 memory leaking detection,發現自己沒有犯任何「基本錯誤」後才敢出手。

「大便噴了香水還是大便」,應該最能解釋修復 bug 的重要性吧?

切記,這九條並不能涵蓋所有的 bug,有時也要適時變通,否則就太過於教條主義了!

沒有留言:

張貼留言