我花兩週抓蟲同事只花兩天
最近為了處理一個 vm crach issue,前後花了兩週,最後還是靠資深的同事才在茫茫大海裡把蟲抓出來。雖然硬要找藉口的話,可以說中間同時要處理其他專案沒法太專心,但這次也的確有種「即使自己不用做其他專案,這個 issue 的確花了超過自己預期太多的時間」,所以決定痛定思痛梳理一下自己這次在鬼打牆什麼東西,順便聊聊兼推廣一本我一直以來很喜歡的書 "Effective Debugging: 66 Specific Ways to Debug Software and Systems" by Diomidis Spinellis。
除錯的兩種策略
除錯的對象百百種,方式、原則也可以說是百百種。其中一個粗略的分法可以分成「假說與實證」與「大量觀察」。
策略一:假說與實證
這種思維方式,是基於自己的知識與手上握有的資訊,明確假定臭蟲出現的原因,不斷蒐集證實原因的資訊來驗證假說。蒐集資訊的方式最基本也最重要的作法,大概就是要有實驗組與控制組,不斷透過調整實驗組來驗證假說的預測能力;基本上就是實驗室實驗基礎方法。
策略二:大量觀察
嚴格來說這種思維其實也是基於假說與實證下去做實驗與觀察,不過是基於較沒頭緒或是對於自己的假說不是這麼有信心的時候,刻意編織更大的「網」來捕捉有用的線索。常見於縝密與龐大的實驗計畫,或是開啟 verbose debugging mode ,先蒐集大量資訊再說。
兩種策略各有優劣
兩種策略各有優劣,算是互相補足,時間、精力、資源充足時通常會雙管齊下;實務上則是常常側重其中一種,但也不會完全捨棄另外一種策略。個人經驗中,執行對應的策略時有一些關鍵步驟,算是個人的小撇步,一起摘要在下面比較表中。
策略 | 精確具體的假說 | 大量觀察 |
---|---|---|
泛用性 | 較低,往往需要領域專業直覺 | 較高,對成因不一定有太多想法時也可以用 |
效率 | 較高,假說對的時候可以精準快速找出臭蟲 | 較低,通常較耗費時間與資源 |
溝通成本 | 較低,講給別人聽的時候相對容易理解 | 較高,他人需要花更多精力了解 context |
風險 | 假說方向錯誤時幾乎不會有任何進展 | 有可能觀察後還是看不出頭緒 |
執行關鍵 | 具體寫下自己對臭蟲原因的假說 | 具體寫下自己擬定的實驗、測試計畫 |
在「假說法」中,具體寫下自己對臭蟲原因的假說,其實本質上跟「黃色小鴨法」(Rubber duck debugging)是類似的概念,不過除了撰寫的過程中可能可以激發靈感以外,具體寫下原因,也有助於自己在驗證的過程中飄走、注意力不夠集中導致亂無章法。幾個原則可以檢視自己寫下的描述是否夠「具體」:
- 問題可能出現在哪類功能上? (如果程式無法明確區分功能模組,這可能是一個軟體 bad smell)
- 這個功能可能在哪些 class 裡有相關實做,甚至自己想得到可能是哪個 method 裡面有相關實做?
- 我可以有哪些方式檢查這個功能、class 與 method?有對應的 test case 、 function test 或是 unit test 可以用嗎?
- 我決定用哪些方式來檢查這個功能、class 與 method?
在「大量觀察法」中,決定實驗方向時停在「功能」與 function test 的層級即可;如果有辦法問到更小的問題,可以考慮回頭採用「假說法」來節省時間與資源。至於「具體」寫下實驗、測試計畫時則是可以依據下面幾個原則來判斷是不是夠「具體」:
- 我的實驗測試計畫有沒有涵蓋我想要測試的功能
- 我的實驗測試計畫有沒有涵蓋我想測試的變因
- 我的實驗測試計畫有沒有一次只改一個變因
- 我的實驗測試計畫有沒有一個已知可以運作的很好的 golden sample 或是 benchmark 可以參考?
- 我的實驗測試計畫有沒有 100% 重現 bug 的方式?
- 我有沒有為了我的實驗測試計畫給實驗步驟寫下具體的 checking list
和 Effective Debugging 一書互相參照
我在 debugging 比較沒進展或是頭緒的時候,非常喜歡回頭參照 Effective Debugging: 66 Specific Ways to Debug Software and Systems by Diomidis Spinellis 這本書。這本書整理了 66 個程式除錯的策略,其中第一章 High-Level Strategies 也有一些和上述策略可以互相參照的策略。例如 item 3 "Confirm That Preconditions and Postconditions Are Satisfied"、item 4 "Drill Up from the Problem to the Bug or Down from Program's Start to the Bug" 與 item 5 "Find the Difference between a Known Good System and a Failing One"。題外話是這本書真的很棒,除了 high level 策略以外,也還有各種面向下擬定的策略,例如 "minimize the turnaround time from your changes to their result"。非常適合自己在程式除錯撞牆的時候,一條一條檢視自己是不是真的窮盡所有方式來分析問題了。
有機會我也想好好聊聊這本書與整理自己各種策略(嗯我知道自己在立旗)。
檢討時間到惹
扯了很多終於要回到這裡的初衷:檢討這次找蟲鬼打牆的原因。根據上面的列出的原則,我認為我沒有認真做好「我的實驗測試計畫有沒有一個已知可以運作的很好的 golden sample 或是 benchmark 可以參考」這個原則。
選了一個不夠接近的 benchmark
是說當初收到的 bug (叫它問題 bug A 好了)是一個 vm networking 類型的 bug。 networking issue 可說是公認最難處理的 bug 類型之一,理由是重現 bug 的環境常常無法 100% reproduce,導致 bug 很難在另外一方(例如用戶之於 RD)被穩定地可靠地 reproduce、所以也很難 claim 自己真的對症下藥了。
我收到 bug report 的時候,因為對方宣稱能夠在某個版本的 kernel 出現這個問題,但其實他回報的內容是用某個 rc kernel 做的,我基於某些原因直接採用他的說法、使用某版本的 kernel 而沒有自己重編某 rc kernel;除此之外,我好死不死剛好踩到另外一個「長得很像但其實不是同一個 bug」(叫它問題 B)的 bug,所以一開始我以為我的 networking 環境的確重現了對方宣稱的問題,但其實我是重現了問題 B,整個就是雞同鴨講。
當初如果稍微不嫌麻煩的話去重編某 rc kernel 的話我就不用花兩週只要花兩三天啊啊啊啊。
過大的溝通成本
另外當初因為有點雞同鴨講,所以我有採用了「大量觀察法」來試著重現問題,這樣等同於在雞同鴨講的情況下另外再墊高了溝通成本,導致我延遲很多時間才發現「喔原來你其實只用 rc kernel 就回報 bug 了啊啊啊啊啊啊啊」。
小心大腦多工模式
也因為我一開始採用了「大量觀察法」,但又想要縮短得到結論的時間,所以忍不住 multi tasking,結果常常忘了自己「做到哪裡」與「忘了剛才有沒有沒檢查到的項目」,反而浪費更多自己的時間與精力,事後想到「還要再做一次一樣的事情喔?」內心有很大的排斥,而排斥感本身又很吃意志力,最後會打擊到自己的士氣 XD
至於 multi-tasking 的技巧也很值得開一篇來特別聊聊(又在立旗),這篇就先不多囉唆太多。大致上 multi-tasksing 不是不行,而是一定要小心注意力用盡導致「一下忘了自己做到哪裡」的窘境,例如「使用清單」。但最好其實盡量不要 multi-tasking 啦,人類大腦好像是不太適合做多工。