調試是一門科學,任何不懂原理就進行的操作都是耍流氓
1947年,一只小飛蛾飛進了繼電器,導致設備發生故障,于是,現場一位計算機科學家Grace Hopper 把導致程序發生故障的問題稱為Bug,把消滅臭蟲的行為,稱為Debug(在一個單詞前面加上De,通常表示反面的意思,如compress和decompress),后來這個叫法漸漸成了計算機術語。
今天的我們,如果遵循TDD原則進行開發,那么我們的開發的過程,其實就是先寫不能通過的測試用例,制造Bug,然后再寫生產代碼,然所有測試用例都可以通過,從而消滅所有已知的Bug。由此看來,說Debug是軟件開發非常重要的一個過程,一點都不為過。
自學會編程以來,消滅的臭蟲不計其數,有的只需幾秒鐘即可將其消滅,有的則持續數日,而且似乎每個十分難纏的Bug,都會在定位了很久很久之后,在自己某個靈(ren)光(pin)一(bao)現(zha)的時刻,突然就work了!以至于現在遇到Bug,都會告訴自己,別慌,解決它只是時間問題。
直到最近偶然看到了這本書——《調試九法》,這是我看到的第一本講調試方法論的書,回想自己之前在解決Bug時的經常遇到的手足無措,像無頭蒼蠅一般四處亂撞,最后瞎貓碰上死耗子般地解決了Bug,我毫不猶豫地買下這本書。
是時候需要一套系統的調試理論了。
希望本文介紹的這套調試學的知識,可以幫助你在今后遇到Bug時,思路更加清晰,心情更加淡定,從容不迫地去解決問題。
Bug,按照其產生的原因,可以分為兩種:
而我們這里要介紹的調試方法,既適用于調試,也適用于故障檢修,這些技術不關心問題如何產生,而是告訴你如何找到問題發生的具體原因。
下面我將分享我讀完這本書之后,形成的一套調試方法論,如果你已經迫不及待地想去讀這本書,不妨把下面這段內容作為書的導讀。
這套方法論可以總結成一句話:
調試是一門科學,任何不懂原理就進行的操作都是耍流氓。
這句話可以分為兩個步驟進行實踐:
準備工作:
尋找原因并解決Bug
下面對這些法則逐一進行詳細地介紹。
理解系統是定位Bug的前提條件。
我之前遇到程序拋出異常時,經常就把異常信息貼到網上搜,然后把網上的解決方案執行一遍,有時work了,就大吉大利,可大多數時候,是不work的,原因很簡單,也許對方的JDK版本跟你的不一樣,也許你們倆只是報錯信息相同,但是拋異常的原因不同,更大的可能,對方的解決方案本來就行不通。
這就能理解為什么理解系統是定位Bug的前提了,如果你在定位一個JDK異常,那么至少你要掌握Java SE吧,如果你能掌握JVM的垃圾回收原理、類加載機制,自然更好;而如果你在定位一個支付系統為什么沒有把賬打到客戶的賬戶,那么你得了解支付的流程吧。
總之,在開始調試之前,我們得弄清楚,調試是一門科學,而不是一門概率學,你需要理解整個系統,才能夠進行調試。有以下方法可以幫助你理解這個系統:
可以說,理解系統的最終目的就是為了了解工作流程,了解了工作流程,你才能夠從整體的角度來觀察,不然就像井底之蛙,以為問題一定出在自己這個模塊,而其實問題是在上游的某個模塊里。這一點,和《程序員的思維修煉》中提到的,“專家從整體進行思考”的觀點不謀而合。
這里當然不是讓你去問人家插頭插了沒,插頭在這里是泛指一切讓產品正常運行的基本要求。這些基本要求通常我們都認為理所當然是正常的,可事實有時并非如此。
比如你的一個系統,需要在配置界面配置白名單,不然上游的請求就會被拒絕,那么當出現問題時,你應該首先去檢查一下這個白名單配置了沒,因為對方有可能是個新手。
甚至當出現一些不可理喻的錯誤時,你要去軟件的運行目錄下,比如Tomcat的webapps目錄,看看軟件包是不是完整。
當你替換新代碼上去后,發現Bug依然存在時,不妨上去看看正在運行著的,是不是還是舊代碼。
書中把這條規則放到了倒數第三條,我這里把它放到第二條,原因很簡單,通常我們在發現Bug或者別人跟自己說這里有Bug時,心里都會慌,都會緊張,所以不妨先檢查一下插頭,緩解一下自己緊張的心情,同時也強迫你從整體的視角進行觀察,不會局限在一個小模塊里。
這幾乎是一個下意識的動作,就算你之前沒讀過這本書,在遇到Bug時,你也會去嘗試重現它,原因很簡單:
有些問題很好重現,而有些呢,卻是要在特定的輸入的情況下才會出現的。
我們犯的絕大多數錯誤是在重現的方式上,作者對重現提出了兩條原則:
現在我們可以重現Bug了,直覺告訴我要在那個地方進行一個字符串編碼的轉換,且慢,在進行這個武斷的嘗試之前,先來看看《福爾摩斯》是怎么說的:
主觀臆斷的人,總是為了套用理論而扭曲事實,而不是用理論來解釋事實。
猜測只是為了確定搜索的重點,但是在開始修復之前要觀察確認你的猜測。所以在我們修改代碼之前,還是看一下發生錯誤時的日志信息,還可以調試一下代碼,在必要的時候打開源碼深入研究一下,確定確實是字符串編碼的問題,再去修改代碼。
有人說,那我直接改代碼,然后看結果不就知道是不是字符串編碼的問題了。當然不是,要知道一個問題的產生可能是由多處地方的代碼引起的,也許解決完這個字符串編碼的問題,還需要解決另一個問題,才能把整個問題解決呢?如果你在修改前就沒進行觀察,就會認為這次修改毫無意義,這樣整個調試過程就會陷入死局。
記住,調試是一門科學,任何不懂原理就進行的操作都是耍流氓。
系統通常都是由很多個模塊組成的,這也就要求我們要檢查很多個模塊的日志才能夠確定問題發生的原因。尤其是現在流行的微服務框架,一筆業務出現問題,你需要到很多個服務的機器上去找日志。
但是,如果你的業務執行是線性的,也就是說如果節點A執行失敗,那么節點A之后的也都會執行失敗,那么你就可以采用二分法的方式來定位了。要知道,在1到100里猜一個數字,最多也就需要7次。
采用二分法的方式,你將逐步縮小嫌疑的范圍,最終找到問題的根因。
當然,如果問題是由多個子問題引起的,那么記住,找到一個,消滅一個,這就是所謂的分而治之。
通過觀察,你認為你的修改方法會起作用,但如果實際上你修改完代碼之后,并沒有起到任何作用,那么請你馬上改回去,以免這個修改引入了新的Bug。
把你調試過程中的操作和結果按順序全部記錄下來,方便你在發現做了那么多處修改依然沒有解決問題時,進行回溯,反思自己的操作有沒有不對的地方。
上面的調試規則,都是從問題出發,去尋找犯錯的代碼。但有時候反過來也許會更好。
你可以找到最新一個可以正常運行的版本,然后對比現在這個版本和那個版本之間的差別,通過分析改動的代碼,來分析是哪塊代碼導致的問題。
有時候問題比較緊急,這時候不妨問一下專家,正如《程序員的思維修煉》中提到的,專家依靠直覺,他們往往會一針見血的給你指出問題的地方。
如果你對系統有一定理解的情況下,可以上軟件供應商的官網、谷歌、StackOverflow等網站尋找相關的資料。
在求助的過程中,你只需要描述問題的癥狀,如果對方沒有要求,那么不要給他講自己的理論,以免將對方帶入自己的思維定式。
而在給他人描述問題的同時,你自己可能也會得到啟發。
以上就是我看完《調試九法》這本書之后總結的一套調試方法論,當然還是建議大家看一下原著,說不定會有新的收獲。不過書中列舉了大量的例子,多的讓我感覺有些冗余,建議大家看的時候,先看每一章節的開頭,和每章結尾的小總結,看完之后有不理解的,再去看每章中間的案例。
本書的例子雖然大多數都是關于工程技術的,但是里面的一些想法還是可以借鑒到生活中去。比如,夫妻吵架了,表面看上去是因為丈夫不愿意洗碗,但是如果你能從全局的角度去觀察,你就知道,其實是因為丈夫情人節時沒有給夫人買禮物。
最后再回過頭來看這些規則,其實我們在工作和生活中時不時都會用到,但是我們之前一直沒有一個系統的理論體系,在掌握了書中介紹的調試規則之后,我們在今后定位錯誤根源時,會更加井井有條,從容不迫。
原文轉自:http://blog.csdn.net/hzy38324/article/details/78639329