嵌入式開發人員可用的最強大的錯誤壓縮工具之一是斷言宏。盡管斷言很強大,但卻很少看到它被實現,并且在使用它的情況下,實現要么有缺陷,要么不正確。以下七個技巧不僅有助于闡明何時何地使用斷言,而且還有助于闡明如何開始正確使用它。
技巧1 – 記住斷言的定義
斷言對于許多開發人員來說是一個令人困惑的話題,因為他們很容易以一種并非設計用于的方式使用斷言。 關于斷言的最清晰定義是:“斷言是程序中特定點的布爾表達式,除非程序中存在錯誤,否則它將為真。”
檢查上述斷言定義的開發人員應注意三個關鍵點:
斷言將表達式評估為真或假
斷言是對代碼中特定點的系統狀態的假設
斷言正在驗證系統假設,如果不正確,則表明代碼中存在錯誤
技巧2 – 使assert來驗證函數的前置條件
斷言在按合同設計的環境中工作得很好,其中嵌入式開發人員已經明確定義了函數的先決條件。 斷言可用于檢查函數的輸入是否滿足前提條件。以下代碼片段為例:
函數的狀態輸入應屬于定義的系統狀態。如果 State 在有效狀態之外,那不是錯誤而是bug!斷言可用于驗證狀態有效的假設,如下圖所示:
如果狀態不小于最大值,則斷言表達式將被評估為假,然后程序執行將停止。 停止程序執行使開發人員可以很容易地立即看到代碼出錯的地方,而不是等很久以后。
技巧3 – 使用斷言來驗證函數的后置條件
斷言還可用于驗證有關按合同設計環境中函數輸出的假設。例如,如果先前定義的System_StateSet 函數返回 SystemState 變量,開發人員會期望它也在預期范圍內。可以使用斷言來監視錯誤,如下圖所示:
檢查上面的代碼,嵌入式開發人員可能會覺得這些檢查毫無價值。剛剛設置的 SystemState怎么會大于 SYSTEM_STATE_MAX?答案是它不應該是這樣,這就是為什么如果它確實發生了變化,可能是通過中斷或并行線程,斷言將立即標記錯誤。
技巧4 – 不要使用斷言進行錯誤處理
在記住斷言的定義后,開發人員應該牢記斷言是用于檢測錯誤而不是用于錯誤處理。錯誤處理是旨在響應不正確的用戶輸入和意外事件序列的軟件。錯誤預計會在系統中發生,但僅僅因為輸入無效并不意味著代碼中存在錯誤。錯誤處理應與錯誤搜尋分開。不正確使用斷言的一個典型例子是在嘗試打開文件進行讀取時檢查文件指針。圖4顯示了一個示例。
讀者可以清楚地看到,嘗試打開文件的結果取決于文件系統和用戶數據的狀態,與代碼中的錯誤完全無關。而不是斷言,嵌入式開發人員應該編寫一個錯誤處理程序,如果文件不存在,它會創建它,它將一些默認的可用數據用于進一步發生在代碼中的操作。
技巧5 – 斷言用于開發而非生產
斷言宏的初衷是在開發期間啟用它,然后在生產中禁用它。啟用和禁用斷言是使用宏 NDEBUG 完成的。正確實現的斷言在禁用時應該對嵌入式系統幾乎沒有影響。問題是,如果在開啟它們的情況下執行測試,應該這樣做以捕獲任何錯誤,現在關閉它們會導致交付的產品處于與測試狀態不同的狀態。
斷言確實占用了一些代碼空間,但更重要的是它們需要幾個時鐘周期來評估它們的布爾表達式。資源有限的裸機系統可能會因關閉斷言而嚴重影響其執行時間,從而導致生產系統中出現新的錯誤。開發團隊需要決定是否值得承擔風險。另一種方法是啟用斷言并將其輸出重定向到系統日志,以便輕松識別任何揮之不去的錯誤,但可能不建議暫停系統。
技巧6 – 不要讓斷言產生副作用
assert 的默認實現將允許嵌入式開發人員將可執行代碼作為布爾表達式的一部分包含在內。例如,狀態變量可以作為傳遞給 assert 的表達式的一部分來實現。如果傳遞給 assert 的表達式有副作用,即它改變了嵌入式系統的狀態,禁用斷言將改變系統的行為。開發人員應確保他們的表達式沒有副作用,因為他們可能會在系統中添加睡眠時間錯誤,該錯誤只會喚醒生產代碼。
技巧7 – 斷言應該占代碼的1 % — 3%
對于代碼庫中應該存在多少斷言,每個開發人員都有自己的看法。可以商定的一個數字是代碼庫中斷言的百分比應該大于零。斷言為開發人員提供了一種在代碼庫中出現錯誤時發現錯誤的好方法。調試是開發嵌入式系統最大的浪費時間和令人沮喪的組件之一。無論開發人員的人數是1%、3%還是5%,都可以利用斷言來發揮自己的優勢,讓開發嵌入式軟件變得更加愉快。如果有的話,我們知道有 0% 不是正確的解決方案!
技巧8 – 使用斷言作為可執行代碼注釋
斷言會產生很好的評論!一個寫得很好的表達式可以準確地告訴開發人員他們在代碼中的給定點應該期望什么。開發人員應該構建他們的斷言,以便更清楚地了解系統中正在發生的事情,這反過來將有助于減少錯誤。
結論
斷言是一個了不起的工具,被太多的嵌入式開發人員忽略了。本文探討的 7 個技巧只是如何正確使用斷言的冰山一角。你可以采取的下一步是在測試臺上設置并開始使用斷言,并研究它們在真實嵌入式系統中的工作方式。