嵌入式軟件Bug從哪來(lái),怎么去
1.軟件問(wèn)題從哪來(lái)
軟件缺陷問(wèn)題千千萬(wàn)萬(wàn),主要是需求、實(shí)現(xiàn)、和運(yùn)行環(huán)境三方面。
1.1 需求描述偏差
客戶角度的描述,在經(jīng)過(guò)業(yè)務(wù)對(duì)接、產(chǎn)品經(jīng)理的轉(zhuǎn)述,最終呈現(xiàn)的軟件需求可能已經(jīng)偏離了原始的述求,開(kāi)發(fā)人員基于自身經(jīng)驗(yàn)的理解偏差,開(kāi)發(fā)過(guò)程缺乏有效的溝通及監(jiān)督,導(dǎo)致最終的軟件功能與客戶的核心訴求存在偏差。
1.2 異常處理機(jī)制不完善
嵌入式軟件必定是運(yùn)行在特定的硬件設(shè)備,硬件本身或環(huán)境問(wèn)題等特殊干擾,開(kāi)發(fā)人員因經(jīng)驗(yàn)不足缺乏風(fēng)險(xiǎn)評(píng)估,面對(duì)電腦是無(wú)法全方位猜測(cè)、模擬各種異常環(huán)境下的差異,最終導(dǎo)致設(shè)備在特定場(chǎng)景下運(yùn)行異常。
1.3 軟件開(kāi)發(fā)能力不足
嵌入式系統(tǒng)的復(fù)雜度與開(kāi)發(fā)人員的能力矛盾,導(dǎo)致軟件本身的邏輯存在缺陷。
2.軟件開(kāi)發(fā)與軟件問(wèn)題
關(guān)于軟件bug的來(lái)源,排除不可控的外界因素,與軟件開(kāi)發(fā)人員相關(guān),或者開(kāi)發(fā)人員可以減少問(wèn)題的發(fā)生的可能,從軟件開(kāi)發(fā)角度解決的方案如下:
2.1 重視需求分析
軟件開(kāi)發(fā)就是寫(xiě)程序,并設(shè)法使之運(yùn)行,這是個(gè)錯(cuò)誤的想法。軟件結(jié)果與客戶期望不一致,需求問(wèn)題不全是軟件開(kāi)發(fā)的鍋。大多數(shù)情況下客戶的原始述求不會(huì)直接到軟件開(kāi)發(fā),軟件開(kāi)發(fā)沒(méi)法去反訴找客戶確認(rèn),只能通過(guò)軟件的實(shí)現(xiàn)形式去甄別不合理的,或者針對(duì)客觀環(huán)境、研發(fā)團(tuán)隊(duì)的基礎(chǔ)去評(píng)估風(fēng)險(xiǎn)。
比如客戶要求可以設(shè)備可以定時(shí)1秒采集一次溫度,精度要求0.0001攝氏度;或者要求數(shù)據(jù)采集持續(xù)采集24h后,每天12:00準(zhǔn)點(diǎn)TCP上報(bào)后臺(tái)服務(wù)器。這就需要考慮溫度傳感器的精度、RTC喚醒以及TCP聯(lián)網(wǎng)時(shí)間、24小時(shí)采樣數(shù)據(jù)的存儲(chǔ)。如果硬件資源或者客觀環(huán)境無(wú)法實(shí)現(xiàn),盲目承諾客戶,或者開(kāi)始編碼,最終結(jié)果可想而知。
軟件開(kāi)發(fā)是個(gè)人的任務(wù),但開(kāi)發(fā)前多溝通確認(rèn),進(jìn)行風(fēng)險(xiǎn)評(píng)估反饋,減少開(kāi)發(fā)的無(wú)用功,也是對(duì)開(kāi)發(fā)人員的基本要求。
2.2 積累行業(yè)經(jīng)驗(yàn)
嵌入式產(chǎn)品都是針對(duì)某個(gè)細(xì)分行業(yè),見(jiàn)多識(shí)廣,才能預(yù)判可能出現(xiàn)的異常,開(kāi)發(fā)階段有針對(duì)性的去處理,或者提前告知使用者去規(guī)避。有時(shí)候經(jīng)驗(yàn)比技術(shù)能力重要。
2.3 提高開(kāi)發(fā)水平
軟件開(kāi)發(fā)水平,首先是個(gè)人能力,熟悉軟件SDK的應(yīng)用,相關(guān)的操作系統(tǒng)、設(shè)計(jì)模式、調(diào)試方法等。軟件開(kāi)發(fā)能力大多數(shù)情況下決定軟件質(zhì)量和可維護(hù)性,這是個(gè)長(zhǎng)期學(xué)習(xí)提高的過(guò)程,如果一定要提供捷徑,那就是多閱讀優(yōu)秀的開(kāi)源代碼。
2.4 先設(shè)計(jì)再編碼
軟件開(kāi)發(fā)不能隨心所欲,先明確方案和大概的實(shí)現(xiàn)流程,胸有成竹,然后再開(kāi)始編碼,完善細(xì)節(jié)。這理論沒(méi)毛病,但真正執(zhí)行起來(lái)卻比較難,大多數(shù)情況下都只在乎軟件出結(jié)果,而實(shí)際上方案不合理,后期修修補(bǔ)補(bǔ)更浪費(fèi)時(shí)間。如果制度和時(shí)間不允許,個(gè)人在紙上畫(huà)畫(huà)框圖和結(jié)構(gòu),先構(gòu)思再開(kāi)發(fā)也能彌補(bǔ),起碼不至于南轅北轍。
2.5 編碼規(guī)范
編碼規(guī)范是軟件開(kāi)發(fā)團(tuán)隊(duì)合作的標(biāo)準(zhǔn),嵌入式行業(yè)可以參考“華為技術(shù)C語(yǔ)言編程規(guī)范”,但實(shí)際開(kāi)發(fā)過(guò)程,和前面的先設(shè)計(jì)再編碼一樣,各種不可控因素,比如項(xiàng)目進(jìn)度壓力和開(kāi)發(fā)者水平與認(rèn)知的差異,導(dǎo)致有編碼規(guī)范卻無(wú)法嚴(yán)格執(zhí)行。隨著軟件工程規(guī)模的擴(kuò)大,軟件交期、代碼同步、重構(gòu)或交接,其風(fēng)險(xiǎn)也逐漸放大。存在編碼規(guī)則并不能解決問(wèn)題,只有強(qiáng)制執(zhí)行才有意義。
2.6 代碼缺陷靜態(tài)檢查與單元測(cè)試
軟件質(zhì)量是項(xiàng)目成敗的關(guān)鍵點(diǎn)之一,在開(kāi)發(fā)周期有限,人力資源不足的情況下,使用工具實(shí)現(xiàn)代碼自動(dòng)掃描,分析出潛在隱患點(diǎn),可從源頭減少軟件bug,比如cppCheck、PC-lint等,實(shí)現(xiàn)代碼自動(dòng)靜態(tài)分析,或者人工視檢,有效規(guī)避簡(jiǎn)單的軟件風(fēng)險(xiǎn)。
如果可能,最佳的選擇是單元測(cè)試,單元測(cè)試比可交付成果本身更重要,文檔注釋不全時(shí),單元測(cè)試就是設(shè)計(jì)文檔;單元測(cè)試定義的API和用法,以及可能的使用風(fēng)險(xiǎn)點(diǎn),就是最佳的參考范例;不足100%的覆蓋率就是玩忽職守,開(kāi)發(fā)人員應(yīng)該全權(quán)負(fù)責(zé)測(cè)試自己造出的產(chǎn)品。依靠后期的黑盒測(cè)試發(fā)現(xiàn)問(wèn)題,其消耗的人力物力,是編寫(xiě)單元測(cè)試的幾倍,而且單元測(cè)試可以反復(fù)的自動(dòng)測(cè)試。不過(guò)這種情況更多的是存在于開(kāi)發(fā)理論中??梢詤⒖嘉⑿殴娞?hào) 嵌入式系統(tǒng) 的《代碼的保養(yǎng)》第二章。
3.前期減少問(wèn)題
軟件問(wèn)題的解決,有些不是個(gè)人能解決的,需要協(xié)調(diào)溝通,或者與研發(fā)團(tuán)隊(duì)的整體風(fēng)格、制度有關(guān)。個(gè)人能決定的是軟件具體邏輯,這也是體現(xiàn)個(gè)人技術(shù)能力的重點(diǎn)。
3.1 C語(yǔ)言基礎(chǔ)
- 多看優(yōu)秀代碼,學(xué)習(xí)其技巧。
- 使用帶參數(shù)檢測(cè)的接口,比如優(yōu)先選擇snprintf,少用sprintf,其它str前綴的如strncmp也是,但要明白這類接口和memcmp區(qū)別。不同的編譯器表現(xiàn)不一致,平時(shí)也要多關(guān)注。
在GCC中編譯運(yùn)行(設(shè)備):
char str[5];
int ret = snprintf(str, 3, "%s", "abcdefg");
//ret = 7 ,str = ab
char str[99];
int ret = snprintf(str, 99, "%s", "abcdefg");
//ret = 7 ,str = abcdefg
注:snprintf的返回值為字符串的長(zhǎng)度,且寫(xiě)入的字符串后面帶有‘\0’結(jié)束符。
在VC中編譯運(yùn)行:
char str[5];
int ret = snprintf(str, 3, "%s", "abcdefg");
//ret = -1 ,str = abc [后面不會(huì)自動(dòng)補(bǔ)\0結(jié)束符]
- 注意函數(shù)返回類型,避免類型強(qiáng)制轉(zhuǎn)換導(dǎo)致調(diào)用判斷異常,有些編譯器對(duì)隱示類型轉(zhuǎn)換直接報(bào)錯(cuò),因?yàn)樗_實(shí)存在風(fēng)險(xiǎn)。
- 合理的使用sizeof、struct、union、weak等關(guān)鍵字,增加代碼的可讀性和可擴(kuò)展性。
- 參數(shù)使用前,如數(shù)組小標(biāo),指針變量使用前必須先判斷是否合法。
- 浮點(diǎn)數(shù)不能直接進(jìn)行==和!=比較,等等,這些細(xì)節(jié)太多,可以參考《C陷阱與缺陷》。
- 講的都會(huì),說(shuō)的都對(duì),但真實(shí)際寫(xiě)代碼,就容易各種小問(wèn)題,主要還是態(tài)度問(wèn)題,缺乏自我檢查、自測(cè)的步驟,依靠測(cè)試發(fā)現(xiàn)bug去驅(qū)動(dòng)研發(fā)調(diào)試修復(fù)是大忌。
3.2 動(dòng)態(tài)內(nèi)存
- 盡量做到申請(qǐng)與釋放在同一個(gè)函數(shù),申請(qǐng)內(nèi)存后,先判斷是否申請(qǐng)成功,再進(jìn)行其它操作。
- 內(nèi)存申請(qǐng)與釋放之間有特殊情況return,要注意釋放。
- 釋放結(jié)構(gòu)體指針前,注意該變量?jī)?nèi)部是否還有指針變量動(dòng)態(tài)申請(qǐng)空間,先釋放內(nèi)部,再釋放外部。
- 關(guān)于內(nèi)存申請(qǐng)與釋放,或使用越界是C語(yǔ)言的劣勢(shì),如果設(shè)備堆空間足夠大,可以在申請(qǐng)時(shí)額外多申請(qǐng)固定空間,記錄申請(qǐng)函數(shù)、長(zhǎng)度、并在首尾標(biāo)記,后續(xù)釋放時(shí)檢查內(nèi)存區(qū)首尾標(biāo)記是否被覆蓋;或者查詢是哪些函數(shù)申請(qǐng)的內(nèi)存始終沒(méi)有被釋放。
3.3 跨平臺(tái)問(wèn)題
- 使用系統(tǒng)API前先判斷自身傳入?yún)?shù)的有效性和范圍等是否符合要求,一般系統(tǒng)API是庫(kù)文件,使用錯(cuò)誤更難發(fā)現(xiàn)問(wèn)題。
- 針對(duì)不同的平臺(tái)常用的接口,務(wù)必增加適配層隔離,便于調(diào)試和后續(xù)移植。比如有的平臺(tái)中斷(SDK提供的中斷回調(diào)不一定是硬件中斷)不支持串口日志。
3.4RTOS系統(tǒng)特性
- 多任務(wù)的競(jìng)爭(zhēng),在RTOS系統(tǒng)中,需要注意全局函數(shù)、全局變量的使用,避免互相競(jìng)爭(zhēng)影響,對(duì)公共函數(shù)盡量做到可重入設(shè)計(jì),具體實(shí)現(xiàn)方案請(qǐng)關(guān)注微信公眾號(hào) 嵌入式系統(tǒng) 的《基于RTOS的軟件開(kāi)發(fā)理論》 。
- 中斷與任務(wù)的調(diào)度關(guān)系 請(qǐng)關(guān)注微信公眾號(hào) 嵌入式系統(tǒng) 的《基于RTOS的軟件開(kāi)發(fā)理論》。
- 合理分配任務(wù)??臻g和消息隊(duì)列的深度,函數(shù)內(nèi)部盡量少用大數(shù)組。
3.5 個(gè)人素養(yǎng)
軟件編碼完成,不是能編譯就收工了,其功能是否符合預(yù)期,開(kāi)發(fā)人員自己檢查是最高效的,很多問(wèn)題都是開(kāi)發(fā)不仔細(xì),或者很簡(jiǎn)單的C基礎(chǔ)應(yīng)用錯(cuò)誤,這不是技術(shù)問(wèn)題而是心態(tài)??梢远嗫纯撮_(kāi)源代碼,或者《C專家編程》等。
4.后期解決問(wèn)題
如果軟件問(wèn)題不可避免,該如何去修復(fù)解決呢?
一般來(lái)說(shuō)100%出現(xiàn)的問(wèn)題都比較容易解決,找到相關(guān)代碼仔細(xì)檢查或者加點(diǎn)日志就能發(fā)現(xiàn)問(wèn)題。難處理的是小概率出現(xiàn)的問(wèn)題,穩(wěn)定復(fù)現(xiàn)它就是成功的一半。
4.1 問(wèn)題復(fù)現(xiàn)
穩(wěn)定復(fù)現(xiàn)問(wèn)題才能快速對(duì)問(wèn)題進(jìn)行定位、解決以及驗(yàn)證,如何提高復(fù)現(xiàn)的概率?
- 模擬復(fù)現(xiàn)條件,問(wèn)題只在特定的條件下出現(xiàn),對(duì)于依賴外部輸入的條件難以滿足,可以考慮程序里預(yù)設(shè)直接進(jìn)入對(duì)應(yīng)狀態(tài),或者軟件內(nèi)部進(jìn)行極端的壓力測(cè)試。
- 提高相關(guān)代碼執(zhí)行頻率,進(jìn)行某個(gè)操作才可能出現(xiàn)異常,人工持續(xù)操作,或者軟件頻繁執(zhí)行相應(yīng)的功能,提高問(wèn)題點(diǎn)的執(zhí)行頻率,加快復(fù)現(xiàn)速度。
- 增大測(cè)試樣本量 ,個(gè)別樣機(jī)難出現(xiàn),如果條件允許,可以使用多個(gè)設(shè)備同時(shí)進(jìn)行測(cè)試。一般情況下試產(chǎn)就是為了發(fā)現(xiàn)這類問(wèn)題。
4.2 問(wèn)題定位
縮小排查范圍,確認(rèn)引入問(wèn)題的函數(shù)或代碼片段。
- 打印日志 ,日志是最直接、簡(jiǎn)單的調(diào)試方法,在問(wèn)題的可疑點(diǎn)增加日志輸出,以此來(lái)追蹤程序執(zhí)行流程以及關(guān)鍵變量的值,觀察是否與預(yù)期相符。
- 版本回退,使用版本管理工具時(shí)可以通過(guò)不斷回退版本,驗(yàn)證前面版本的情況,定位首次引入該問(wèn)題的版本,針對(duì)該版本的改動(dòng)進(jìn)行排查。
- 二分注釋,“二分注釋”類似二分查找法的方式注釋掉部分代碼,以此判斷問(wèn)題是否由注釋掉的這部分代碼引起。具體為將與問(wèn)題不相干的部分代碼注釋掉一半,看問(wèn)題是否解決,未解決則注釋另一半,如果解決則繼續(xù)將注釋范圍縮小一半,以此類推逐漸縮小問(wèn)題的范圍,確定是哪一塊代碼導(dǎo)致這個(gè)問(wèn)題。
- 硬件協(xié)助,借助示波器、邏輯分析儀分析波形,必要時(shí)也請(qǐng)硬件協(xié)助分析;問(wèn)題樣機(jī)與正常樣機(jī)的主控對(duì)調(diào),看問(wèn)題是否隨芯片走。尤其是涉及驅(qū)動(dòng)方面的問(wèn)題,比如充電、中斷、復(fù)位、外設(shè)通信調(diào)試異常時(shí)。
- 仿真調(diào)試 ,在線調(diào)試可以起到和打印LOG類似的作用,適合排查程序崩潰類的BUG,當(dāng)程序陷入異常中斷候可以直接STOP查看call stack以及內(nèi)核寄存器的值,快速定位問(wèn)題點(diǎn),不過(guò)這需要硬件支持。
- 三板斧,使用最多的是前面三種方法,這三板斧足以應(yīng)付大部分業(yè)務(wù)邏輯問(wèn)題;偶爾請(qǐng)硬件協(xié)助解決驅(qū)動(dòng)問(wèn)題,日常開(kāi)發(fā)中的問(wèn)題都能解決。個(gè)別系統(tǒng)層面或者架構(gòu)不合理導(dǎo)致的深沉問(wèn)題,要么花時(shí)間死磕coredunmp,要么聯(lián)系原廠FAE協(xié)助,一般芯片方案商都提供技術(shù)支持。
4.3 問(wèn)題修復(fù)與回歸測(cè)試
- 縮小范圍確定問(wèn)題代碼,再排查具體的函數(shù),修復(fù)問(wèn)題點(diǎn)。
- 有些問(wèn)題屬于架構(gòu)層面,比如和RTOS相關(guān)的競(jìng)爭(zhēng)關(guān)系,這種就無(wú)法定位到具體問(wèn)題代碼點(diǎn),只能在宏觀上依靠經(jīng)驗(yàn)或操作系統(tǒng)理論去解決。
- 解決后需要進(jìn)行回歸測(cè)試,確認(rèn)問(wèn)題是否不再出現(xiàn),也要確認(rèn)修改不會(huì)引入其他新問(wèn)題。
4.4 復(fù)盤(pán)
- 一般情況下最后發(fā)現(xiàn)原因都是很簡(jiǎn)單的幾句話,比如數(shù)據(jù)越界或者循環(huán)體多執(zhí)行一次,看起來(lái)都是很簡(jiǎn)單的基礎(chǔ)用法,因?yàn)橐痪溴e(cuò)誤可能需要幾周時(shí)間來(lái)發(fā)現(xiàn)解決,為什么當(dāng)初寫(xiě)錯(cuò)而且沒(méi)檢查發(fā)現(xiàn)呢?
- 總結(jié)問(wèn)題產(chǎn)生的原因及解決方法,今后如何防范,對(duì)其他平臺(tái)否值得借鑒,做到舉一反三,從失敗中吸取經(jīng)驗(yàn)。
5.心得
業(yè)務(wù)指示開(kāi)發(fā)、測(cè)試驅(qū)動(dòng)開(kāi)發(fā),這一荒謬方法論,體現(xiàn)在部門(mén)合作與職責(zé)不清,整體就是效率低下、互相推諉,在這樣的環(huán)境下開(kāi)發(fā)軟件也很累。