[設(shè)計開發(fā)]高可靠性嵌入式系統(tǒng)固件設(shè)計策略
本文針對如何編寫易理解、易維護的優(yōu)秀代碼進行了討論,為程序員提供了一些非常實用的編程指導.文中指出,函數(shù)功能應該最小化,代碼封裝便于程序維護,消除冗余能夠提高程序的可靠性,適當?shù)闹貥?gòu)能夠降低維護過程中程序熵增大的速度,提高程序的清晰度,而遵循一定的標準并采用適當?shù)臋z驗工具則會進一步保證代碼的可靠性.
一些非正式調(diào)查顯示,60%到70%的固件編寫者都持有電子工程師學位,這一學歷背景在幫助理解所開發(fā)的應用的物理層,以及錯綜復雜的硬件時,起到了很好的作用.但大多數(shù)電子工程課程都忽視了軟件工程的教育.當然,教師們會教授如何編程,他們希望每個學生都能精通代碼構(gòu)造,但在他們所提供的教育中,缺乏對構(gòu)造可靠系統(tǒng)所必須的軟件工程關(guān)鍵原則的教育.
也許如今最廣為人知,但卻最少被采用的軟件設(shè)計準則就是保持函數(shù)短小精悍.我曾在一次固件講座中詢問聽眾,多少人在編寫代碼時限制了函數(shù)長度,結(jié)果幾乎沒人舉手.但事實上我們清楚,好的代碼不可能很長.
如果你編寫的函數(shù)超過了50行,即一頁,那么這個函數(shù)已經(jīng)太長.事實上,對于一個超過8或10個阿拉伯數(shù)字的字符串,我們能夠記住的時間很可能無法超過1分鐘.那么又怎能奢望我們能理解一個由成千上萬的ASCII字符構(gòu)成的函數(shù)?對于那些跨了許多頁的程序而言,即使是試圖跟隨程序流程都很困難,甚至幾乎不可能,因為我們必須不斷地翻頁,才能看懂那一個個嵌套著的循環(huán)是用來干什么的.
一個函數(shù)應該只實現(xiàn)一個功能.如果一段代碼過于纏繞不清,拼命地想完成許多不同的功能,那么這樣的代碼就過于復雜,不可能具備可靠和可維護的特性.我見過太多這樣的函數(shù),它們利用多達50個參數(shù)來選擇成打的交互模式,這樣的函數(shù)幾乎都不能可靠地工作.用獨立的方式表達獨立的想法,將每個想法寫成完全清楚的函數(shù).經(jīng)驗告訴我們,當你很難找到一個能夠清楚表達函數(shù)意義的名字時,說明這個函數(shù)的功能已經(jīng)太多了.
封裝代碼
提倡面向?qū)ο缶幊?OOP)的人們一直在倡導“封裝、繼承和多態(tài)”的要求,盡管OOP并不適用于所有應用,封裝卻可以說放之四海而皆準.封裝的意思是將數(shù)據(jù)以及對其進行操作的代碼捆綁進一個實體,這意味著任何其他代碼都不能直接訪問這些數(shù)據(jù).
如果你所開發(fā)的應用中對ROM限制非常嚴格,每個字節(jié)都要仔細斟酌,那么這種應用基本無法封裝.在幾個對成本極端敏感的應用中(例如電子賀卡),將內(nèi)存需求降到最低就至關(guān)重要.但是我們必須承認,這類應用的開發(fā)成本本身就非常昂貴.無論何時,只要你陷入了受字節(jié)限制的開發(fā)條件,那么開發(fā)成本就很可能高得驚人.
大家都知道,利用C++和Java編程時可以進行封裝.事實上,用C和匯編開發(fā)時,同樣可以進行封裝.封裝時要注意,所有全局變量都必須在使用到該變量的函數(shù)或模塊內(nèi)定義,并保證該變量不被其他程序訪問.但封裝并不僅僅意味著數(shù)據(jù)隱藏.一個完全封裝好的對象具有很高的內(nèi)聚性 (cohesion),無需涉及任何與其無關(guān)的行為就能完成任務.同時,這樣的對象還具備異常安全和多線程安全性.我們可以把這樣的對象或函數(shù)看作一個完整的功能性黑盒子,只需很少的外部支持,或者根本無需任何支持.
一個封裝得較好的序列號處理程序可能需要一個中斷服務程序,用以向循環(huán)緩沖器傳送它接收到的字符,需要一個get_data()程序從數(shù)據(jù)結(jié)構(gòu)中提取數(shù)據(jù),同時還需要一個is_data_available()函數(shù),用于測試接收到的字符.它還能處理緩沖溢出、序列號缺失、奇偶錯誤以及所有其它可能出現(xiàn)的錯誤條件,因此,這種程序是可重入的.
對代碼進行封裝之后的一個必然結(jié)果就是消除了代碼之間的依賴性.高內(nèi)聚必然伴隨著低耦合,換句話說,就是封裝后的代碼對其它行為的依賴性較小.我們都曾讀過這樣的代碼,一些看起來簡單的操作卻與成打的其它模塊糾纏不清.這時,即使只做最簡單的設(shè)計修改,維護人員也不得不在成千上萬行代碼中跟蹤變量和功能,而這肯定會把他們逼瘋.
消除冗余
斯坦福大學的研究人員對160萬行Linux代碼進行的研究發(fā)現(xiàn),即使是無害的冗余也往往和程序缺陷(bug)高度關(guān)聯(lián)(參看www.stanford.edu/~engler/p401-xie.pdf).
研究人員將冗余定義為一段無效的代碼,例如:1. 將一個變量賦給它自己;2. 初始化或設(shè)置一個變量后卻從不使用它;3. 死碼;4. 在復雜的條件判斷語句中,一個子語句的邏輯條件已經(jīng)被在其之前的子語句涵蓋,因而該語句永遠不會被求值.這些研究員十分聰明,他們并未將某些特殊情況列入冗余范疇,例如用于設(shè)置一個存儲映射I/O端口的代碼.這類操作看起來多余,但其實是有用的.
即使那些不會造成程序缺陷的無害冗余也會引發(fā)問題,因為這些包含冗余的函數(shù)中出現(xiàn)硬錯誤的可能性比不含冗余代碼的函數(shù)要高出50%.冗余代碼的出現(xiàn)意味著設(shè)計人員思路不清,因此很有可能在附近出現(xiàn)其它錯誤.
此外,還要小心成塊拷貝的代碼.我個人十分贊成代碼重用,也鼓勵開發(fā)人員繼續(xù)開發(fā)那些已經(jīng)測試過的大塊源代碼,但是很多時候,設(shè)計人員往往在拷貝代碼時,并沒有對被拷貝代碼的含義做足夠的研究.你真的能確保,即使這段代碼是來自該程序的另一部分,所有的變量也都是按你期望的方式初始化的嗎?會不會有極小的可能在程序中出現(xiàn)互斥,引發(fā)死鎖或者競爭?
我們拷貝代碼是為了節(jié)約開發(fā)時間,但這是有代價的.我們必須比研究自己正在編寫的新代碼更仔細地研究這些拷貝來的代碼.當lint或者編譯器警告發(fā)現(xiàn)了未使用的變量時,必須引起注意.這可能是一個信號,意味著程序中潛伏著更多嚴重的錯誤.
減少實時代碼
實時代碼不但易出錯、編寫成本高昂,而且調(diào)試成本可能更高.如果可能,最好將對執(zhí)行時間要求嚴格的段落轉(zhuǎn)移到一個單獨的任務或者程序段中去.如果在整個程序中處處滲透著時間問題,那么會令每一個字都變得難以調(diào)試.
如今我們所構(gòu)建的系統(tǒng)比過去龐大得多,也復雜得多,但我們所采用的調(diào)試工具的調(diào)試能力卻不如十年前的調(diào)試工具.盡管處理器的速度已經(jīng)暴增至接近無窮快,在線仿真器還是我們的首選調(diào)試器,其中包括實時跟蹤電路、事件定時器,甚至性能分析工具.今天,我們身邊處處充斥著BDM或者JTAG調(diào)試器.這些工具用于解決程序上的問題還可以,但他們基本上沒有提供任何資源用于解決時域問題.
還要請大家記住以下幾個在安排開發(fā)時間表上的經(jīng)驗規(guī)則:一個系統(tǒng)負荷達到90%的系統(tǒng),其開發(fā)時間是負荷小于或等于70%系統(tǒng)的兩倍;如果系統(tǒng)的負荷增加到95%,那么開發(fā)時間將增大到三倍.實時應用項目的開發(fā)是十分昂貴的,高負荷的實時項目尤其如此.
編寫優(yōu)雅流暢的代碼
這里強調(diào)的是流暢,而非跳轉(zhuǎn).要構(gòu)造流暢的程序,就應該避免使用continue、goto、break或過早的return.這些語句本來都是十分有用的構(gòu)造,但他們通常會降低函數(shù)的透明性.極限編程以及其它一些敏捷編程方法強調(diào)了重構(gòu)(即重新編寫劣質(zhì)代碼)的重要性.這其實并非新概念,理順并維護編寫惡劣的代碼模塊比維護一段結(jié)構(gòu)漂亮的代碼模塊要昂貴得多.
重構(gòu)的狂熱追求者們要求我們重新編寫所有那些可以被改善的代碼,這就顯得過于吹毛求疵.我們的工作是要用一種可盈利的方式創(chuàng)造能夠成功的產(chǎn)品.追求完美這一目標不應凌駕于所有其它的考慮之上.但仍有一些函數(shù)寫得太差,必須重寫.
如果你害怕編輯某個函數(shù),或者如果每次你對這個函數(shù)做一點修改,它就會出問題,那么就該重構(gòu)這個函數(shù).如果你作為一個專業(yè)開發(fā)人員,憑著你經(jīng)過良好訓練的直覺發(fā)現(xiàn),我們最好別碰這段代碼,因為沒人敢與之周旋.那么就說明是時候該放下所有其它事,重寫這段代碼,讓它變得易懂、易維護.
熱力學第二定律告訴我們,任何閉合系統(tǒng)如果向更加無序的方向發(fā)展,其熵就會增加.程序也遵循這一令人沮喪的事實.一次又一次的維護過程常常會增加程序的無序性,使下一次的改變更加困難.正如Ron Jeffries所指出的,維護而不重構(gòu)會給每次得到的軟件版本增加一個“混亂因子(m)”,從而增大代碼的熵.這樣,每次修改軟件得到的新版本的維護代價可以看做(1+m)(1+m)(1+m). . .,或者(1+m)×n,其中n是修改發(fā)布的次數(shù).并且隨著我們對程序修整和隨意縮略的次數(shù)越來越多,維護代價會呈指數(shù)上升.這也是為什么有些程序員的小聰明會激怒管理者,讓他們覺得“這個程序簡直一團糟,沒法維護”.
重構(gòu)也需要付出代價,但它能消除混亂因子,使得修改發(fā)布軟件的代價變成線性的1+r+r+r . .
Luke Hohmann倡導“降低老版本的熵”,他指出我們?yōu)榱税l(fā)布產(chǎn)品,常常過于頻繁地做出一些快速修整,這就增大了維護成本.因此,必須還清妄自修整軟件帶來的技術(shù)債務.維護不僅僅是填鴨式地向軟件中添加新功能,它還包含降低軟件在維護過程中自然產(chǎn)生的熵.
同時,重構(gòu)還能將含混不清的邏輯關(guān)系理順.如果現(xiàn)有的代碼纏繞不清、亂七八糟,那么就應重新編寫它,以便更好地說明其含義.此外,還應刪除那些層層嵌套的循環(huán)或條件語句.因為誰也沒有那么聰明,能夠明白那一層層嵌套的IF.程序越透明,就越容易正確.
遵守代碼編寫標準并借助檢查工具
請根據(jù)你公司的固件標準來編寫代碼,并利用正式的代碼檢查工具來增強代碼與標準的符合度并尋找缺陷.在檢查結(jié)束之后再進行測試.用檢驗工具尋找缺陷比人工調(diào)試大約便宜20倍.他們能夠捕捉到你通過傳統(tǒng)測試從未檢查到的各種問題.許多研究都表明,傳統(tǒng)調(diào)試只能檢查約一半代碼!如果在產(chǎn)品發(fā)布前不使用檢驗工具,那么發(fā)布的很可能是一個處處隱含著缺陷的產(chǎn)品.
有趣的是用于構(gòu)造安全性要求高的軟件的DO-178B標準嚴重依賴于所使用的工具來保證每一行代碼都被執(zhí)行,這些代碼覆蓋工具確實是個奇跡,但它們?nèi)匀粺o法取代檢查.
代碼編寫標準和檢查是相輔相成的,任何一方離開另一方都不可能取得成功.而沒有標準和檢查就不可能構(gòu)造出完美的固件.
本文小結(jié)
當我才剛剛開始我的職業(yè)生涯時,一位比我入行早的同事教了我一招:如果這該死的程序能夠工作,那么就別管它.這一招他稱之為基本工程原則.這是一個很有誘惑力的概念,我將其貫徹了許多年.這一原則在硬件設(shè)計中似乎還算有效,但將其用于固件設(shè)計,就將是一場災難.我認為,“還工作得不錯”就意味著項目成功的想法是部分軟件危機的根源所在,然而專業(yè)人士應該明白,對一個軟件發(fā)展的要求與對其功能和操作的要求同等重要.讓我們共同努力,寫出既漂亮又便于維護,而且能夠可靠工作的程序!
參考文獻:
1. Jones, Nigel. "Lint" Embedded Systems Programming, May 2002, p552. Berger, Arnold and Michael Barr. "On-Chip Debug" Embedded Systems Programming, March 2003, p473. Boehm, Barry. Software Engineering Economics. Englewood Cliffs, NJ: Prentice-Hall, 19824. Jones, Capers. Applied Software Measurement: Assuring Productivity and Quality. New York: McGraw-Hill, 19915. Jeffries, Ron. Extreme Programming Installed, Boston: Addison-Wesley, 20006. Ganssle, Jack. "Better, Faster Code" Embedded Systems Programming, August 1998, p1177. Ganssle, Jack. "A Firmware Development Standard" Embedded Systems Programming, March 1998, p1298. Ganssle, Jack. "Firmware Development Standard, Part 2" Embedded Systems Programming, April 1998, p97
[設(shè)計開發(fā)]高可靠性嵌入式系統(tǒng)固件設(shè)計策略
全部回復(0)
正序查看
倒序查看
現(xiàn)在還沒有回復呢,說說你的想法