前言:工作三年了,工作內容主要是嵌入式軟件開發和維護,用的語言是C,畢業后先在一家工業自動化控制公司工作兩年半,目前在一家醫療儀器公司擔任嵌入式軟件開發工作。軟件開發中,難免不產生bug;產品交付客戶使用后,難免不產生問題,那么關於bug分析和異常處理則是軟件開發和維護中無法躲避的工作內容。工作至今,我一直在思考關於bug分析和異常處理,有沒有一些原則性、規律性的東西可循,以減少bug,提高bug分析的效率,對於一些異常,基於什么原則進行處理,才能達到客戶的要求。這些問題每個行業、每個職位上的人都會有不同的想法,但是大家的目的是一樣的,即提高產品的穩定性、可靠性、可用性和易用性。我暫且說說,自己的一些想法吧,不一定正確,但是算是自己想法的一種梳理吧,其實自己希望通過這種梳理增強自己關於這些問題的認識,促進自己的進一步思考。
備注:因為只是自己的一些想法,就暫且不考慮行文了,呵呵
原則一:自己寫的軟件中肯定存在bug
這一原則適用於軟件開發和編碼階段,她讓自己保持一顆謙虛謹慎的心,那么自己在設計和編碼階段就會考慮可能出現的問題,對於可能出現的問題,采取防御式編碼。譬如:
- 在嵌入式軟件中,不使用動態分配內存,這樣就杜絕了內存泄露的問題,雖然這樣做浪費了內存資源,但是相比后續分析內存泄露的問題帶來的資源消耗和產生的嚴重產品問題,浪費的這點內存還是很划算的;
- 不要相信自己是個沒有“筆誤”的家伙,因此,在書寫if判斷的時候,自己總是會按照if(42 == a)這樣的方式去寫;
- 不要相信自己的是個“仔細”的家伙,因此,每寫完一段代碼,我都會習慣性地ctrl+s和編譯,這樣就可以及時發現語法錯誤和警告,此時,代碼修改的不多,因此問題很好被定位和解決,如果等到自己碼完幾百行代碼以后,再去編譯,處理編譯錯誤和警告,可能會花費更長的時間。我相信迭代式的編譯,步步為營是個不錯的想法,呵呵;其實,也要采取迭代式的測試,即每實現一個功能就要去測試,待測試通過后,再去實現其余的功能,最好不要等到所有功能都實現以后,再開始測試,否則,自己花在調試和測試的時間會遠遠超過自己的預期。(之所以有人會實現了所有功能以后,再開始測試和調試,就是“太相信”自己了,^_^);
- 不要太相信軟件會按照自己的預期工作,因此,在設計和編碼階段,在代碼中插入調試信息,是個非常棒的想法。如果有調試信息,那么在調試時,就能很方便地看出程序是否按照自己的設計預期在工作,如果沒有,也能通過調試信息,發現問題的端倪,至少能縮小問題的原因范圍。當然,增加調試信息,會增加系統的開銷,降低系統的性能,而且真正的產品中可能不允許太多的調試信息或者日志,那么可以利用編譯開關的方法,將調試信息分級,譬如分為正常流程信息、錯誤流程信息、嚴重錯誤信息等等,自己調試或者測試時,打開全部的信息顯示或記錄;產品發布前,只打開錯誤或者嚴重錯誤信息,這樣做,如果產品在客戶使用中出現了錯誤,工程師也能根據這些信息記錄,縮小問題原因的范圍,為后續的問題原因分析,提供非常寶貴和重要的依據。
原則二:盡早集成調試
原則一是不要相信自己,那么原則二就是不要太相信隊友,嘻嘻
每個人理解能力和技術能力不同,那么針對某個功能需求,相關人員的理解就可能出現偏差,不同人員關於接口部分的實現細節可能出現不一致。那么當產品集成調試時,也就是問題的高發時期,而且這些問題通常也很難找到原因和解決。可以具體采取一些措施,減少產品集成過程中產生的問題,譬如:
- 盡早進行產品集成。當相關人員實現了產品的某個細節后,就應該立即進行集成測試,這個時候,產品功能很少,即使集成測試出現了問題,問題也比較容易定位和解決,而且在這個集成過程中,增強了相關人員彼此關於需求的理解;當一個細節測試沒有問題后,再進行下一個細節的開發和測試,這樣步步為營的做法,不但減少了產品最終集成可能出現的問題,更重要的是增強了開發人員的安全感,讓開發人員感覺到身邊的那個隊友還是很可靠的嘛,呵呵。
- 盡早讓用戶使用產品。其實,用戶有的時候根本就不清楚自己想要的是什么樣的產品,只有當你把產品擺到用戶的面前,他們才告訴你,他們想要的是那個樣子,他們需要的是那個功能(相信此時很多開發人員都想罵人)。因此,當產品可使用的時候,就要拿到用戶面前,讓用戶使用,讓用戶提意見,然后再繼續開發。一句話,就是不要太相信用戶當初說了什么。
- 將產品需求以文檔或郵件的方式確定下來。當相關人員通過會議確定了一個功能實現細節之后,就埋頭去碼代碼去了,但是集成時,發現對方根本沒有按照當初會議確定的要求去做,對方還辯自己就是按照當初會議確定的方式實現的,沒有證據的工程師真的是百口莫辯,只有淚奔了。但是,如果此時,你能拿出根據當初會議討論的會議紀要文檔,孰對孰錯,就一目了然了!有了文檔,即使用戶抱怨產品有些功能不是自己想要的,你也有證據表示,這些功能當初是經過他們確認過的。
原則三:做總比不做要強
編碼過程中,每當進行異常保護或者異常處理時,是不是都感覺比較痛苦。這些異常保護或者異常處理,現在根本看不到有任何好處,或許以后也永遠用不到,但是卻要增加自己要碼的代碼行數,有點吃力不討好。但是,這些東西如果不做,一旦出現問題,可能是很大的問題,但是如果做了,可能就沒有問題了,從投入產出比來看,只是多敲一些代碼,多花費一些時間而已,換來的卻是,產品更高的可靠性和穩定性。
簡單總結一下:
- 防御式編程,避免“筆誤”;
- 迭代式地編譯、測試和調試;
- 增加調試信息,並對調試信息分等級,通過編譯開關選擇打開或關閉它們;
- 產品中一定要有日志記錄,尤其是操作日志、錯誤日志記錄,這樣,當出現問題時,可以盡可能地縮小問題原因范圍;
- 盡早集成產品,將功能需求以文檔或郵件的方式確定下來;
以上討論的都是如何避免產生bug,下面說說,如何分析bug。
我將bug依據復現的難易程度分為:必現的bug,比較容易復現的bug,很難復現的bug。
對於必現的bug,我通常淡定地稱為其不是bug,因為,通過不斷地復現,不斷地調試,這些bug通常都能被解決,被解決了,還是bug么?
對於比較容易復現的bug,所謂比較容易復現,就是通過不太復雜操作,嘗試幾次、十幾次,現象就可出現的bug,因為復現操作變得復雜,所以,為了每次復現能夠獲得更多的信息,盡量多地增加調試信息,以期望問題復現后,極大地縮小問題原因的范圍。畢竟復現問題是一件頗為繁瑣、枯燥的事情。
對於很難復現的bug,所謂很難復現,就是嘗試了各種復現方法,復現了幾十次,甚至上百次都無法復現的bug。首先,分析造成那個bug的所有可能的原因,然后盡量針對每個可能的原因,增加對應的調試信息記錄,以期望在復現過程中,一旦出現,就能定位問題,不過做到這一點其實是很難的,但是做總比不做強。在第一家公司的時候,我們曾經在交付客戶使用的產品中,增加了bug追蹤信息記錄,以期望找到在測試過程中發現的一個僅出現過一次的bug。
在產品設計和開發過程中,對於異常的處理和保護是保證產品可靠性和可用性的關鍵,下面說說自己關於這些的一些想法:
- 對異常進行危險等級分類,對於不同的等級采取不同的辦法;所采取的辦法通常包括:
- 給用戶以提示,但是不執行用戶的錯誤操作,譬如用戶輸入的參數超出了允許范圍等,參數的格式不滿足要求等;
- 軟件的一部分功能崩潰,要盡量不影響其他部分,譬如上位機軟件崩潰,不能影響下位機設備的正常運行;前端界面的崩潰,不能污染數據庫;
- 自動應急處理,譬如提供冗余設備,當一套設備故障,備用設備立即啟用;提供看門狗功能,設備故障后,盡快重新啟動,並且從硬存儲中恢復距離事故最近的狀態;
- 給用戶提供應急措施,譬如對於可能造成人體傷害的設備,都需要提供一個緊急停止按鈕,該按鈕的標示還要非常醒目;
設計良好產品的日志記錄和錯誤追蹤功能,這樣做的好處是:
- 通過用戶的操作日志,確認用戶是否存在非法操作,以確認事故的責任;
- 錯誤追蹤信息,一方面可以幫助工程師分析產品發生異常的原因,一方面可以幫助產品開發者分析產品使用過程中可能存在的問題,以幫助進一步改進產品;
無論是產品開發過程中,還是產品使用過程中日志記錄和錯誤追蹤功能都非常重要,關於這兩個功能,簡單總結一下自己的經驗:
- 如果是桌面程序,可以將日志記錄和錯誤追蹤作為文本文件保存在硬盤上;
- 如果是嵌入式程序,則可以將日志以編碼的方式記錄在非易失的存儲介質中(非易失的要求是保證當設備掉電或者重啟,以保證日志不會丟失;另外,對該介質的訪問速度有一定要求,不能太慢了,否則會影響設備的性能),然后通過網絡或者其他方式將這些編碼讀出、解析;
- 在保證系統性能的基礎上,日志信息能加多少,就加多少;(當遇到問題時,你會非常慶幸自己有一份非常詳細的日志在手,呵呵)
- 日志需要必須包含兩個要素:時間、做了什么;盡量包含以下要素:操作是否成功、失敗的原因;
- 盡量避免日志的循環往復記錄,譬如系統循環往復地發生一種錯誤,如果每發生一次錯誤,就記錄一條,那么有限的日志記錄空間就只有這一條日志了,留給工程師分析問題的信息就太少了,可以采取這樣一種方式:錯誤發生時記錄一條,錯誤消失時再記錄一條,以此,就可以確認錯誤持續的時間,而且也不會覆蓋掉其他的日志信息;
以上只是自己三年工作以來對於一些問題的思考,囿於自己所從事的行業和經驗,有些想法還不太完善,也不具有普適性,僅算是一種思路的梳理吧,只是希望通過這種梳理能夠幫助自己更深入地認識這些問題,想出更好的解決的辦法,呵呵。