《除錯九法》:除錯是個技術活

yisha7發表於2016-12-24

一般當工程師把一個東西稱為藝術甚至玄學的時候,說明這個東西難度很大,沒有太多規律可以遵循,除錯就屬於此列。幾乎每個程式設計師都有被bug搞到死去活來的經驗,有時候顛來倒去,問題似乎解決了,但也不知道為什麼,就把它當作不可解釋的玄學現象,最煩人的一種bug是偶爾出現難以復現的,學名海森堡bug。

不過畢竟軟體還是屬於科學技術的範疇,除錯也應當是門技術活。《除錯九法》是一本少見的講除錯技術的書,九個普遍性的原則不僅適用於軟硬體開發維護,甚至還能運用到日常生活,為了找到這本書我也是費了點神,幸好圖靈社群還有正版的電子書賣。

說實話,作者舉的很多例子偏硬體,所以理解起來有點隔膜,但是九個原則確實很實用,在我以前的開發除錯經歷中可能也不自覺的總結過一些,但是看了本書,還是有拿到武功心法的感覺。看過之後,下面再把九個原則遍歷一下,加深理解。

1 理解系統

這是最重要的一條原則,要分析bug,自然要理解系統是如何運作的,這就需要學習掌握一些基本原理,對具體的類庫、工具、技術都需要認真的去讀相關文件。我最近幾年才養成了認真讀官方文件的習慣,搜尋引擎的發達,使得我們養成了面向google程式設計的習慣,面對問題,總是想一蹴而就的解決,結果往往是走了很多彎路,因為捨不得花時間看路標。本書還特別強調“逐字逐句”讀手冊,簡直是對浮躁的人當頭棒喝。不理解系統就開始除錯和不理解原理就寫程式碼是一脈相承的,俗稱“面向巧合程式設計”。

2 製造失敗

這條原則講的是復現bug的重要性與方法,一個bug,肯定是在某個特定的條件下發生的,抽絲剝繭找到這個特定的條件,就成功了一半。以我的硬體維護經驗來看,發現bug的人如果能詳細的記錄整個過程是非常有幫助的。有很多bug出現的條件比較苛刻,所以程式設計師的口頭禪之一就是“在我這裡沒問題啊”。針對這類bug,首先最好能找到模擬方法,比如加快軟體執行互動的速度,進行壓力測試等,其次就是在軟體內部能有詳細合理的bug記錄機制,便於從內部找到復現的條件。

3 不要想,而要看

這一條強調的是觀測的重要性,面對現實比胡思亂想重要。語言影響思考,所以遇到bug的口頭禪應該是“我看看“而不是”我猜可能是因為“。我們應該想盡辦法去看清楚出bug的細節,所以成熟的程式中都應該有用於除錯的基礎設施,個人經驗至少日誌是必不可少的,初級程式設計師習慣於完全靠打斷點除錯,問題是斷點本身會改變程式執行的流程(尤其是多執行緒的情況下)。當然,猜測依然是有用的,可以幫我們縮小觀察的範圍,或者至少擬定一個觀察的優先順序,這樣經驗就能發揮作用,但是不管怎樣,經驗不能替代觀測。

4 分而治之

二分查詢法可以把查詢的時間複雜度從線性變成對數,不僅是程式中的查詢演算法,也是除錯時的方法論,一個系統有了這個意識後,關鍵是如何劃分系統,又回到了原則1。至少在打斷點除錯時,要找到出bug的位置,也是可以用二分法的,或者插入日誌記錄時也可以應用這個原則。另外,書中還提到了bug間相互影響的現象,我覺得甚至又bug蹺蹺板的現象,解決之道是一個都不要放過。除錯bug時發現程式碼質量實在太差,重構一下有時候也是有必要的,否則浮沙之上築不了高臺啊,甚至經過有效的重構,bug很自然的就發現並消除了。

5 一次只改一個地方

這條原則類似實驗科學中的對照原則,一次只考察一個變數,比對正常情況和異常情況,一定不能忽略任何一個測試條件的差異。上一條說bug一個都不放過,但也得一條條過,重構的時候也要注意不要對不懂的程式碼亂改一氣,很多程式碼的危險性在於牽一髮而動全身,我覺得這也是修復bug時的最大風險,往往是消除一個bug同時增加幾個bug,還是回到原則1,要理解系統。不過有時候,理解前人的程式碼談何容易,這也啟示我們寫程式碼時要多積德,不要以為程式碼就是給編譯器看的,而應該是給以後維護我們程式碼的人看的。

6 保持審計跟蹤

這條原則講的是記錄的重要性與方法,我想這就像醫生問診一樣,需要問合適的問題,給出具體的有效答案。我的個人經驗是一旦開始除錯bug,可能整個人就徹底陷進去出不來,通過記錄的方式可以把自己從思維的泥潭中抽身出來,不斷修正除錯bug的計劃與方法。對一些專用系統軟體,需要培訓軟體的使用者如何用有效的語言來記錄出現bug的情況,如果沒有記錄,那麼除錯的人就需要用適當的問題幫助使用者喚醒記憶。此外,我覺得半夜調不出的bug需要睡一覺,讓發散思維自動起作用,一般早上就解決了。

7 檢查插頭

這條原則大概說的是所謂”低階失誤“了,類似我媽跟我說電腦音響不響了,我首先得問電源開了沒這種。又比如把main函式寫成mian函式,有一次我打斷點除錯就是不進斷點,後來發現是因為有兩段程式碼比較類似,我斷點打錯地方了。根據場景和經驗,問問自己是否犯了該場景下常見的低階失誤,往往針對能立竿見影的解決很多問題,就像很多電器裝置說明書的故障FAQ中,也會強調檢查插頭。可以說,低階失誤低階的是難度,而不是頻度。

8 獲得全新觀點

這條原則說的是求助和交流,這裡面很重要的是通過他人的觀點來破除自己的思維定勢,所以最好是隻給別人詳細描述現象,不要說自己的猜測,以免汙染別人的判斷。對程式設計師而言,這時候面向google程式設計的力量是驚人的,可能九成的問題都不需要你真正的再去提問,而是找到合適的搜尋詞去搜尋即可,對中國程式設計師而言,用英文搜尋是必須的技能,當然搜到東西后真正理解也是非常重要的,不能見到藥就吃。如果遇到的問題google真的搜不出來,證明你層次稍微高了點,這時候可以上stackoverflow之類的社群提問。

9 如果你不修復bug,它將依然存在

這條原則說的是不要心存僥倖,不要因為bug只是閃現了一下就採取鴕鳥策略,該來的總會來,不是不報,時候未到。當然實際情況可能很複雜,軟體工程本來就是時間、成本和質量的妥協體,但即使放過某些bug,也要分析出這樣可能造成的後果,建立充分的防護機制,因此在很多軟體認證體系中,都會有軟體安全等級標準。這裡還涉及bug復現和舉一反三的問題,需要確認真正找到了bug的原因提供了有效的修復手段,並排查所有類似的問題。比如修復了一個記憶體洩露問題,就要修復所有導致這類洩露的程式碼。

附記:如何寫出適合除錯的程式碼

從除錯的原則反觀,對軟體構建本身也很有幫助,畢竟除錯只是查漏補缺的,最好還是寫出來的程式碼少一些bug,為了除錯的時候方便多預留一些手段。這裡也簡要總結一下個人經驗,可能需要不斷完善。

  1. 程式中應該提供日誌機制,在程式的debug版便於輸出除錯資訊,即使在release版,也要記錄軟體發生問題(如C#中丟擲異常)時的詳細情況;
  2. 要寫出適合人類閱讀的程式碼,否則後續維護的人看都看不懂,要如何除錯?怎麼寫《程式碼大全》中說得最詳細了,我覺得除了各種習慣寫法之外將心比心也是很重要的;
  3. 使用各種類庫或工具時,儘量弄懂原理,仔細的閱讀官方文件,比如有些類庫會詳細說明如何防止記憶體洩露,如何應對多執行緒情況,看清楚再用,就會少種下禍根,至少不用等到除錯的時候再無頭蒼蠅一樣猜測原因;
  4. 寫便於測試的程式碼,這樣在除錯時,容易剝離問題,或者使用二分法,這方面TDD程式設計實踐講得比較充分,我個人也需要加強。

相關文章