在這篇文章中,beck分享了他在linux后臺開發和調試領域的豐富經驗。作為一名從事c語言開發超過十年的從業者,他詳細介紹了調試的挑戰和方法,并強調了開發過程中的關鍵階段。
作者:beck
畢業超過十年了,感慨歲月無情。作為一名從事后臺開發多年的從業者(之前在電信領域工作),我將分享一些常見的開發心得和調試手段。多年使用互聯網的經歷讓我收獲頗豐,但總結的很少。秉承互聯網精神,希望我的經驗能幫助到互聯網另一端的你。由于我主要從事c語言開發,以下經驗也是基于C語言的調試手段。
調試是一個復雜而令人困擾的問題,很少有人能保證自己的代碼完全沒有錯誤。一旦發現問題,就需要進行調試。調試方法多種多樣,從反匯編查看二進制代碼,到使用gdb查看統計信息,再到簡單的打印日志,甚至是自己制造bug讓別人來查。掌握這些方法后,總能找到適合自己的調試方式。
調試的最終目的是找到bug。一個高手曾打過一個有趣的比喻:你找BUG其實就像是福爾摩斯。你在BUG的“案發現場”尋找線索——合格的程序應該有日志、dump內存、計數等基本信息。如果什么都沒有,只能找寫代碼的人自己查。調試就是在眾多信息中抽絲剝繭,找到疑點,反復推演程序運行的代碼,最終定位到那幾行“作案”的代碼。
這個過程非常折磨人,沒有任何眉目時,令人茶飯不思。但一旦找到問題,就像打了雞血般興奮,甚至會陶醉其中。只有真正經歷過這種折磨的人,才能體會到修改問題的快感。
開發的程序通常要經過兩個階段,最終才能上線發布。
在功能開發階段,主要目標是根據業務需求開發程序。僅僅是寫if else嗎?寫程序絕不僅僅如此。如果只是這樣,開發人員的工作將變得更加枯燥和機械化。
做事都講究未雨綢繆,開發程序更應該如此。大學C語言經典教材中定義程序為:程序 = 數據結構 + 算法。但在實際生產過程中,我認為更合適的定義是:程序 = 數據結構 + 算法 + 業務邏輯(計算邏輯)+ 框架。
補充業務邏輯的原因是有意義的程序本身就是某種業務邏輯(計算邏輯)的抽象。完成這個業務邏輯才是最終目的,不要拿一些算法研究的代碼與我爭論。
作為開發人員,測試驅動開發(tdd)是一種很好的思考問題的方式。也許有人聽說過,也許有人用過,如果你覺得使用效果不佳,我可以告訴大家:應該采用測試場景 + 場景驅動開發。是的,僅僅是加入“場景”這個賓語,就能讓開發更有目的性和針對性。
任何一個業務邏輯都可以拆分為多個業務場景。逐一解決和測試這些場景,開發過程其實很簡單。雖然聽起來簡單,但整個過程需要50%的時間思考解決問題場景,20%的時間編碼,30%的時間測試。思考問題的50%的時間,可以在任何時間進行(休息時,地鐵上,班車上…),只要讓自己足夠靜,你就能將整個業務邏輯思考得非常清楚,分解為多個業務場景。對于復雜的業務場景,建議適當做筆記,從全局的業務邏輯考慮:自己細化的結論是否符合所有的業務場景。反復修正,直到正確。
具體編碼時,經過前面的深思熟慮,每個細節都已經很清楚了,可以采用迭代的方式,批量交付小的功能點。
開發階段的關鍵詞總結為:TDD + 迭代。需要更多詳情的同學可以自行百度、谷歌。
在功能調試階段,調試手段有很多,包括走讀代碼、打日志、使用gdb、統計、coredump等,如果有精力也可以進行白盒測試。測試的意圖很明確,就是確認代碼是否按照正確的編碼意圖運行。自己寫的代碼,調試起來相對容易,因為你清楚代碼的本意該如何運行,現在出現了什么問題。
程序員的三大悲劇之一,就是不知道什么時候需要定位其他人寫的bug。定位前必須理解另一位程序員寫這段代碼的意圖,否則無法進行定位。理解其他人的代碼可以通過閱讀代碼了解大致思路,通過日志、gdb或統計信息補充代碼意圖的更多細節,或者修正理解不對的思路。
這個過程可能很枯燥,也可能很有挑戰,試圖通過種種跡象去了解另一位程序員寫代碼的初衷和意圖,有點像窺探人家的隱私!
以上說的很多只是為了說明調試的前提和初衷。一個優秀的程序員會掌握很多調試技巧,也就是很多調試手段來獲取自己想要的信息。獲取的信息越多,就越容易理解程序本身的意圖。
以下是我簡單闡述自己是如何調試程序的,以及如何理解各種工具的,歡迎各位大蝦指點交流:
1) 關于日志如何打好日志絕對是一門學問。日志打印太多會影響后臺程序的性能,打印太少則無法定位問題。更糟糕的是打印到空指針,可能導致程序coredump。
所以日志的技巧是:少,且內容豐富。
如何做到少,就是匯聚。
能否將表達同一個意思的打印減少?
能否在關鍵異常的地方加上統計(輸出統計)?
能否不打?
能否在內存中記錄關鍵信息,在需要時控制其打印時機?
如何做到內容豐富,就是少打描述性詞匯,多打有用的程序運行信息。
方法很多,大家多多思考。并且打印的優化是一個反復優化的過程,不是馬上就能完成的。曾經遇到過一個大牛,測試部提出問題時,他從不親自定位,而是直接讓測試的兄弟執行軟調,將收集的日志給他分析就能解決問題。
2) 關于gdb有大牛說過:“我就是程序,程序就是我”。我常用gdb來檢驗自己對程序的理解。常用的gdb功能包括打印程序運行信息,修改一些內部運行信息,構造復雜場景。
其實很簡單,程序在什么場景下應該有什么樣的行為,我自己必須清楚。必須知道關鍵變量的信息是否正確,周期性地使用gdb確認變量的信息是否正確,然后決定程序是否符合預期在執行。
可靠的程序都有類似的保護機制,但通常需要繁瑣地構造測試條件來觸發這些機制(如檢測到丟包率很高,要告警等)。大多數保護機制都是通過記錄一些狀態后觸發的。
其實,可以使用gdb構造出異常狀態,確認告警機制是否生效。gdb很好地補充了這方面的測試和驗證工作。
3) 關于統計統計信息是關鍵信息匯集的最好例子。數據少,信息明確。
在電信軟件中,很多模塊都通過這樣的信息來“自證清白”,也很容易發現問題出現在哪里。
統計的實質是通過全局變量記錄程序正常和異常點的統計信息,然后通過某種手段輸出出來。
4) 關于coredump大家看到coredump都會頭痛,但coredump也是很好的定位手段。
首先,程序coredump后,會生成詳細的coredump文件,該文件詳細記錄了程序在core之前的運行信息。使用gdb加載這個coredump文件,你想看什么都可以。這只是簡單地使用coredump。
如果遇到復雜的問題,難搞的問題,也可以使用coredump來定位。
比如程序執行到一個十分不常見的代碼分支,然后就core掉了,但目前的輸出信息(如日志等)無法進一步定位問題。
怎么辦?有沒有想過在復現問題的環節,出一個調試版本的程序,在異常分支上主動觸發內存異常,產生coredump,利用coredump信息來確定程序是如何異常的。
5) 關于代碼修改這也是我常用的手段之一,反復對比修改前后的代碼,確認修改代碼的準確性和全面性,反思自己代碼修改是否全面?這里面用到的工具就是beyondcompare。
從事編程多年,偶有所得,記錄于此,希望各位有所收獲!