嵌入式軟件架構(gòu)設(shè)計(jì)_第1頁
嵌入式軟件架構(gòu)設(shè)計(jì)_第2頁
嵌入式軟件架構(gòu)設(shè)計(jì)_第3頁
嵌入式軟件架構(gòu)設(shè)計(jì)_第4頁
嵌入式軟件架構(gòu)設(shè)計(jì)_第5頁
已閱讀5頁,還剩18頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

嵌入式系統(tǒng)軟件架構(gòu)設(shè)計(jì)目錄1. 前言 42. 決定架構(gòu)的因素和架構(gòu)的影響 42.1. 常見的誤解 52.1.1. 小型的系統(tǒng)不需要架構(gòu) 52.1.2. 敏捷開發(fā)不需要架構(gòu) 73. 嵌入式環(huán)境下軟件設(shè)計(jì)的特點(diǎn) 73.1. 和硬件密切相關(guān) 73.2. 穩(wěn)定性要求高 83.3. 內(nèi)存不足 83.3.1. 虛擬內(nèi)存技術(shù) 83.3.2. 兩段式構(gòu)造 93.3.3. 內(nèi)存分配器 103.3.4. 內(nèi)存泄漏 113.4. 處理器能力有限,性能要求高 113.4.1. 抵御新技術(shù)的誘惑 113.4.2. 不要有太多的層次 113.5. 存儲(chǔ)設(shè)備易損壞,速度較慢 123.5.1. 損耗均衡 123.5.2. 錯(cuò)誤恢復(fù) 123.6. 故障成本高昂 134. 軟件框架 144.1. 嵌入式軟件架構(gòu)面臨的問題 144.2. 什么是框架 144.2.1. 軟件復(fù)用的層次 144.2.2. 針對(duì)高度特定領(lǐng)域的抽象 154.2.3. 解除耦合和應(yīng)對(duì)變化 164.2.4. 框架可以實(shí)現(xiàn)和規(guī)定非功能性需求 164.3. 一個(gè)框架設(shè)計(jì)的實(shí)例 174.3.1. 基本架構(gòu) 174.3.2. 功能特點(diǎn) 174.3.3. 分析 184.3.4. 實(shí)際效果 234.4. 框架設(shè)計(jì)中的常用模式 234.4.1. 模板方法模式 234.4.2. 創(chuàng)建型模式 234.4.3. 消息訂閱模式 244.4.4. 裝飾器模式 244.5. 框架的缺點(diǎn) 255. 自動(dòng)代碼生成 265.1. 機(jī)器能做的事就不要讓人來做 265.2. 舉例 265.2.1. 消息的編碼和解碼 265.2.2. GUI代碼 275.2.3. 小結(jié) 285.2.4. GoogleProtocolBuffer 286. 面向語言編程(LOP) 306.1. 從自動(dòng)化代碼生成更進(jìn)一步 306.2. 優(yōu)勢(shì)和劣勢(shì) 326.3. 在嵌入式系統(tǒng)中的應(yīng)用 327. 測(cè)試 337.1. 可測(cè)試性是軟件質(zhì)量的一個(gè)度量指標(biāo) 337.2. 測(cè)試驅(qū)動(dòng)的軟件架構(gòu) 347.3. 系統(tǒng)測(cè)試 347.3.1. 界面自動(dòng)化測(cè)試 347.3.2. 基于消息的自動(dòng)化測(cè)試 367.3.3. 自動(dòng)化測(cè)試框架 367.3.4. 回歸測(cè)試 387.4. 集成測(cè)試 387.5. 單元測(cè)試 387.5.1. 圈復(fù)雜度測(cè)量 417.5.2. 扇入扇出測(cè)量 427.5.3. 框架對(duì)單元測(cè)試的意義 428. 維護(hù)架構(gòu)的一致性 429. 一個(gè)實(shí)際嵌入式系統(tǒng)架構(gòu)的演化 439.1. 數(shù)據(jù)處理 449.2. 窗口管理 449.3. MVC模式 459.4. 大量類似模塊,低效的復(fù)用 469.5. 遠(yuǎn)程控制 469.6. 自動(dòng)化的TL1解釋器 479.7. 測(cè)試的難題 479.8. 小結(jié) 4710. 總結(jié) 48前言嵌入式是軟件設(shè)計(jì)領(lǐng)域的一個(gè)分支,它自身的諸多特點(diǎn)決定了系統(tǒng)架構(gòu)師的選擇,同時(shí)它的一些問題又具有相當(dāng)?shù)耐ㄓ眯?,可以推廣到其他的領(lǐng)域。本課程試圖從嵌入式軟件架構(gòu)設(shè)計(jì)出發(fā),啟發(fā)大家對(duì)軟件架構(gòu)設(shè)計(jì)的理解。本課程的很多內(nèi)容是對(duì)謝老師課程在嵌入式領(lǐng)域的具體闡述。提起嵌入式軟件設(shè)計(jì),傳統(tǒng)的印象是單片機(jī),匯編,高度依賴硬件。傳統(tǒng)的嵌入式軟件開發(fā)者往往只關(guān)注實(shí)現(xiàn)功能本身,而忽視諸如代碼復(fù)用,數(shù)據(jù)和界面分離,可測(cè)試性等因素。從而導(dǎo)致嵌入式軟件的質(zhì)量高度依賴開發(fā)者的水平,成敗系之一身。隨著嵌入式軟硬件的飛速發(fā)展,今天的嵌入式系統(tǒng)在功能,規(guī)模和復(fù)雜度各方面都有了極大的提升。比如,Marvell公司的PXA3xx系列的最高主頻已經(jīng)達(dá)到800Mhz,內(nèi)建USB,WIFI,2D圖形加速,32位DDR內(nèi)存。在硬件上,今天的嵌入式系統(tǒng)已經(jīng)達(dá)到甚至超過了數(shù)年前的PC平臺(tái)。在軟件方面,完善的操作系統(tǒng)已經(jīng)成熟,比如Symbian,Linux,WinCE?;谕晟频牟僮飨到y(tǒng),諸如字處理,圖像,視頻,音頻,游戲,網(wǎng)頁瀏覽等各種應(yīng)用程序?qū)映霾桓F,其功能性和復(fù)雜度比諸PC軟件不遑多讓。原來多選用專用硬件和專用系統(tǒng)的一些商業(yè)設(shè)備公司也開始轉(zhuǎn)換思路,以出色而廉價(jià)的硬件和完善的操作系統(tǒng)為基礎(chǔ),用軟件的方式代替以前使用專有硬件實(shí)現(xiàn)的功能,從而實(shí)現(xiàn)更低的成本和更高的可變更,可維護(hù)性。決定架構(gòu)的因素和架構(gòu)的影響架構(gòu)不是一個(gè)孤立的技術(shù)的產(chǎn)物,它受多方面因素的影響。同時(shí),一個(gè)架構(gòu)又對(duì)軟件開發(fā)的諸多方面造成影響。軟件架構(gòu)軟件架構(gòu)軟件規(guī)模生命周期非功能性需求非功能性需求團(tuán)隊(duì)組成開發(fā)模式下面舉一個(gè)具體的例子。摩托車的發(fā)動(dòng)機(jī)在出廠前必須通過一系列的測(cè)試。在流水線上,發(fā)動(dòng)機(jī)被送到每個(gè)工位上,由工人進(jìn)行諸如轉(zhuǎn)速,噪音,振動(dòng)等方面的測(cè)試。要求實(shí)現(xiàn)一個(gè)嵌入式設(shè)備,具備以下基本功能:安裝在工位上,工人上班前開啟并登錄。通過傳感器自動(dòng)采集測(cè)試數(shù)據(jù),并顯示在屏幕上。記錄所有的測(cè)試結(jié)果,并提供統(tǒng)計(jì)功能。比如次品率。如果你是這個(gè)設(shè)備的架構(gòu)師,哪些問題是在設(shè)計(jì)架構(gòu)的時(shí)候應(yīng)該關(guān)注的呢?常見的誤解小型的系統(tǒng)不需要架構(gòu)有相當(dāng)多的嵌入式系統(tǒng)規(guī)模都較小,一般是為了某些特定的目的而設(shè)計(jì)的。受工程師認(rèn)識(shí),客戶規(guī)模和項(xiàng)目進(jìn)度的影響,經(jīng)常不做任何架構(gòu)設(shè)計(jì),直接以實(shí)現(xiàn)功能為目標(biāo)進(jìn)行編碼。這種行為表面上看滿足了進(jìn)度,成本,功能各方面的需求,但是從長遠(yuǎn)來看,在擴(kuò)展和維護(hù)上付出的成本,要遠(yuǎn)遠(yuǎn)高于最初節(jié)約的成本。如果系統(tǒng)的最初開發(fā)者繼續(xù)留在組織內(nèi)并負(fù)責(zé)這個(gè)項(xiàng)目,那么可能一切都會(huì)正常,一旦他離開,后續(xù)者因?yàn)閷?duì)系統(tǒng)細(xì)節(jié)的理解不足,就可能引入更多的錯(cuò)誤。要注意,嵌入式系統(tǒng)的變更成本要遠(yuǎn)遠(yuǎn)高于一般的軟件系統(tǒng)。好的軟件架構(gòu),可以從宏觀和微觀的不同層次上描述系統(tǒng),并將各個(gè)部分隔離,從而使新特性的添加和后續(xù)維護(hù)變得相對(duì)簡單。舉一個(gè)城鐵刷卡機(jī)的例子,這個(gè)例子在前面的課程中出現(xiàn)過。簡單的城鐵刷卡機(jī)只需要實(shí)現(xiàn)如下功能:一個(gè)While循環(huán)足以實(shí)現(xiàn)這個(gè)系統(tǒng),直接就可以開始編碼調(diào)試。但是從一個(gè)架構(gòu)師的角度,這里有沒有值得抽象和剝離的部分呢?計(jì)費(fèi)系統(tǒng)。計(jì)費(fèi)系統(tǒng)是必須抽象的,比如從單次計(jì)費(fèi)到按里程計(jì)費(fèi)。傳感器系統(tǒng)。傳感器包括磁卡感應(yīng)器,投幣器等。設(shè)備可能更換。故障處理和恢復(fù)。考慮到較高的可靠性和較短的故障恢復(fù)時(shí)間,這部分有必要單獨(dú)設(shè)計(jì)。未來很可能出現(xiàn)的需求變更:操作界面。是否需要抽象出專門的Model來?以備將來實(shí)現(xiàn)View。數(shù)據(jù)統(tǒng)計(jì)。是否需要引入關(guān)系型數(shù)據(jù)庫?如果直接以上面的流程圖編碼,當(dāng)出現(xiàn)變更后,有多少代碼可以復(fù)用?不過,也不要因此產(chǎn)生過度的設(shè)計(jì)。架構(gòu)應(yīng)當(dāng)立足滿足當(dāng)前需求,并適當(dāng)?shù)目紤]重用和變更。敏捷開發(fā)不需要架構(gòu)極限編程,敏捷開發(fā)的出現(xiàn)使一些人誤以為軟件開發(fā)無需再做架構(gòu)了。這是一個(gè)很大的誤解。敏捷開發(fā)是在傳統(tǒng)瀑布式開發(fā)流程出現(xiàn)明顯弊端后提出的解決方案,所以它必然有一個(gè)更高的起點(diǎn)和對(duì)開發(fā)更嚴(yán)格的要求。而不是倒退到石器時(shí)代。事實(shí)上,架構(gòu)是敏捷開發(fā)的一部分,只不過在形式上,敏捷開發(fā)推薦使用更高效,簡單的方式來做設(shè)計(jì)。比如畫在白板上然后用數(shù)碼相機(jī)拍下的UML圖;用用戶故事代替用戶用例等。測(cè)試驅(qū)動(dòng)的敏捷開發(fā)更是強(qiáng)迫工程師在寫實(shí)際代碼前設(shè)計(jì)好組件的功能和接口,而不是直接開始寫代碼。敏捷開發(fā)的一些特征:針對(duì)比傳統(tǒng)開發(fā)流程更大的系統(tǒng)承認(rèn)變化,迭代架構(gòu)簡潔而不混亂強(qiáng)調(diào)測(cè)試和重構(gòu)嵌入式環(huán)境下軟件設(shè)計(jì)的特點(diǎn)要談嵌入式的軟件架構(gòu),首先必須了解嵌入式軟件設(shè)計(jì)的特點(diǎn)。和硬件密切相關(guān)嵌入式軟件普遍對(duì)硬件有著相當(dāng)?shù)囊蕾囆?。這體現(xiàn)在幾個(gè)方面:一些功能只能通過硬件實(shí)現(xiàn),軟件操作硬件,驅(qū)動(dòng)硬件。硬件的差異/變更會(huì)對(duì)軟件產(chǎn)生重大影響。沒有硬件或者硬件不完善時(shí),軟件無法運(yùn)行或無法完整運(yùn)行。這些特點(diǎn)導(dǎo)致幾方面的后果:軟件工程師對(duì)硬件的理解和熟練程度會(huì)很大程度的決定軟件的性能/穩(wěn)定性等非功能性指標(biāo),而這部分一向是相對(duì)復(fù)雜的,需要資深的工程師才能保證質(zhì)量。軟件對(duì)硬件設(shè)計(jì)高度依賴,不能保持相對(duì)穩(wěn)定,可維護(hù)性和可重用性差軟件不能離開硬件單獨(dú)測(cè)試和驗(yàn)證,往往需要和硬件驗(yàn)證同步進(jìn)行,造成進(jìn)度前松后緊,錯(cuò)誤定位范圍擴(kuò)大。針對(duì)這些問題,有幾方面的解決思路:用軟件實(shí)現(xiàn)硬件功能。選用更強(qiáng)大的處理器,用軟件來實(shí)現(xiàn)部分硬件功能,不僅可以降低對(duì)硬件的依賴,在響應(yīng)變化,避免對(duì)特定型號(hào)和廠商的依賴方面都很有好處。這在一些行業(yè)里已經(jīng)成為了趨勢(shì)。在PC平臺(tái)也經(jīng)歷了這樣的過程,比如早期的漢卡。將對(duì)硬件的依賴獨(dú)立成硬件抽象層,盡可能使軟件的其他部分硬件無關(guān),并可以脫離硬件運(yùn)行。一方面將硬件變更甚至換件的風(fēng)險(xiǎn)控制在有限的范圍內(nèi),另一方面提高軟件部分的可測(cè)試性。穩(wěn)定性要求高大部分嵌入式軟件都對(duì)程序的長期穩(wěn)定運(yùn)行有較高的要求。比如手機(jī)經(jīng)常幾個(gè)月開機(jī),通訊設(shè)備則要求24*7正常運(yùn)行,即使是通訊上的測(cè)試設(shè)備也要求至少正常運(yùn)行8小時(shí)。為了穩(wěn)定性的目標(biāo),有一些比較常用的設(shè)計(jì)手段:將不同的任務(wù)分布在獨(dú)立的進(jìn)程中。良好的模塊化設(shè)計(jì)是關(guān)鍵WatchDog,Heartbeat,重新啟動(dòng)失效的進(jìn)程。完善而統(tǒng)一的日志系統(tǒng)以快速定位問題。嵌入式設(shè)備一般缺乏有力的調(diào)試器,日志系統(tǒng)尤其重要。將錯(cuò)誤孤立在最小的范圍內(nèi),避免錯(cuò)誤的擴(kuò)散和連鎖反應(yīng)。核心代碼要經(jīng)過充分的驗(yàn)證,對(duì)非核心代碼,可以在監(jiān)控或者沙盒中運(yùn)行,避免其破壞整個(gè)系統(tǒng)。舉例,Symbian上的GPRS訪問受不同硬件和操作系統(tǒng)版本影響,功能不是非常穩(wěn)定。其中有一個(gè)版本上當(dāng)關(guān)閉GPRS連接時(shí)一定會(huì)崩潰,而且屬于knownissue。將GPRS連接,HTTP協(xié)議處理,文件下載等操作獨(dú)立到一個(gè)進(jìn)程中,雖然每次操作完畢該進(jìn)程都會(huì)崩潰,對(duì)用戶卻沒有影響。雙備份這樣的手段較少采用內(nèi)存不足雖然當(dāng)今的嵌入式系統(tǒng)的內(nèi)存比之以K計(jì)數(shù)的時(shí)代已經(jīng)有了很大的提高,但是隨著軟件規(guī)模的增長,內(nèi)存不足的問題依然時(shí)時(shí)困擾著系統(tǒng)架構(gòu)師。有一些原則,架構(gòu)師在進(jìn)行設(shè)計(jì)決策的時(shí)候可以參考:虛擬內(nèi)存技術(shù)有一些嵌入式設(shè)備需要處理巨大的數(shù)據(jù)量,而這些數(shù)據(jù)不可能全部裝入內(nèi)存中。一些嵌入式操作系統(tǒng)不提供虛擬內(nèi)存技術(shù),比如WinCE4.2每個(gè)程序最多只能使用32M內(nèi)存。對(duì)這樣的應(yīng)用,架構(gòu)師應(yīng)該特別設(shè)計(jì)自己的虛擬內(nèi)存技術(shù)。所謂的虛擬內(nèi)存技術(shù)的核心是,將暫時(shí)不太可能使用的數(shù)據(jù)移出內(nèi)存。這涉及到一些技術(shù)點(diǎn):引用計(jì)數(shù),正在使用的數(shù)據(jù)不能移出。使用預(yù)測(cè),預(yù)測(cè)下一個(gè)階段某個(gè)數(shù)據(jù)的使用可能性。基于預(yù)測(cè)移出數(shù)據(jù)或者提前裝入數(shù)據(jù)。占位數(shù)據(jù)/對(duì)象。高速緩存。在復(fù)雜數(shù)據(jù)結(jié)果下緩存高頻率使用的數(shù)據(jù),直接訪問??焖俚某志没脱b載。下圖是一個(gè)全國電信機(jī)房管理系統(tǒng)的界面示意圖:

每個(gè)節(jié)點(diǎn)下都有大量的數(shù)據(jù)需要裝載,可以使用上述技術(shù)將內(nèi)存占用降到最低。兩段式構(gòu)造在內(nèi)存有限的系統(tǒng)里,對(duì)象構(gòu)造失敗是必須要處理的問題,失敗的原因中最常見的則是內(nèi)存不足(實(shí)際上這也是對(duì)PC平臺(tái)的要求,但是在實(shí)際中往往忽略,因?yàn)閮?nèi)存實(shí)在便宜)。兩段式構(gòu)造就是一種常用而有效的設(shè)計(jì)。舉例來說:CMySimpleClass:classCMySimpleClass

{

public:

CMySimpleClass();

~CMySimpleClass();

...

private:

intSomeData;};CMyCompoundClass:classCMyCompoundClass

{

public:

CMyCompoundClass();

~CMyCompoundClass();

...

private:

CMySimpleClass*iSimpleClass;};在CMyCompoundClass的構(gòu)造函數(shù)里初始化iSimpleClass對(duì)象。CMyCompoundClass::CMyCompoundClass()

{

iSimpleClass=newCMySimpleClass;

}當(dāng)創(chuàng)建CMyCompoundClass的時(shí)候會(huì)發(fā)生什么呢?CMyCompoundClass*myCompoundClass=newCMyCompoundClass;為CMyCompoundClass的對(duì)象分配內(nèi)存

調(diào)用CMyCompoundClass對(duì)象的構(gòu)造函數(shù)在構(gòu)造函數(shù)中創(chuàng)建一個(gè)CMySimpleClass的實(shí)例構(gòu)造函數(shù)結(jié)束返回一切看起來都很簡單,但是如果第三步創(chuàng)建CMySimpleClass對(duì)象的時(shí)候發(fā)生內(nèi)存不足的錯(cuò)誤怎么辦呢?構(gòu)造函數(shù)無法返回任何錯(cuò)誤信息以提示調(diào)用者構(gòu)造沒有成功。調(diào)用者于是獲得了一個(gè)指向CMyCompoundClass的指針,但是這個(gè)對(duì)象并沒有構(gòu)造完整。如果在構(gòu)造函數(shù)中拋出異常會(huì)怎么樣呢?這是個(gè)著名的噩夢(mèng),因?yàn)槲鰳?gòu)函數(shù)不會(huì)被調(diào)用,在創(chuàng)建CMySimpleClass對(duì)象之前如果分配了資源就會(huì)泄露。關(guān)于在構(gòu)造函數(shù)中拋出異??梢詥沃v一個(gè)小時(shí),但是有一個(gè)建議是:盡量避免在構(gòu)造函數(shù)中拋出異常。所以,使用兩段式構(gòu)造法是一個(gè)更好的選擇。簡單的說,就是在構(gòu)造函數(shù)避免任何可能產(chǎn)生錯(cuò)誤的動(dòng)作,比如分配內(nèi)存,而把這些動(dòng)作放在構(gòu)造完成之后,調(diào)用另一個(gè)函數(shù)。比如:AddressBook*book=newAddressBook()If(!book->Construct()){ deletebook; book=NULL;}這樣可以保證當(dāng)Construct不成功的時(shí)候釋放已經(jīng)分配的資源。在最重要的手機(jī)操作系統(tǒng)Symbian上,二段式構(gòu)造法普遍使用。內(nèi)存分配器不同的系統(tǒng)有著不同的內(nèi)存分配的特點(diǎn)。有些要求分配很多小內(nèi)存,有的則需要經(jīng)常增長已經(jīng)分配的內(nèi)存。一個(gè)好的內(nèi)存分配器對(duì)嵌入式的軟件的性能有時(shí)具有重大的意義。應(yīng)該在系統(tǒng)設(shè)計(jì)時(shí)保證整個(gè)系統(tǒng)使用統(tǒng)一的內(nèi)存分配器,并且可以隨時(shí)更換。內(nèi)存泄漏內(nèi)存泄漏對(duì)嵌入式系統(tǒng)有限的內(nèi)存是非常嚴(yán)重的。通過使用自己的內(nèi)存分配器,可以很容易的跟蹤內(nèi)存的分配釋放情況,從而檢測(cè)出內(nèi)存泄漏的情況。處理器能力有限,性能要求高這里不討論實(shí)時(shí)系統(tǒng),那是一塊很大的專業(yè)話題。對(duì)一般的嵌入式系統(tǒng)而言,由于處理器能力有限,要特別注意性能的問題。一些很好的架構(gòu)設(shè)計(jì)由于不能滿足性能要求,最終導(dǎo)致整個(gè)項(xiàng)目的失敗。抵御新技術(shù)的誘惑架構(gòu)師必須明白,新技術(shù)常常意味著復(fù)雜和更低的性能。即使這不是絕對(duì)的,由于嵌入式系統(tǒng)硬件性能所限,彈性較低。一旦發(fā)現(xiàn)新技術(shù)有和當(dāng)初設(shè)想不同之處,就更難通過修改來適應(yīng)。比如GWT技術(shù)。這是Google推出的Ajax開發(fā)工具,它可以讓程序員像開發(fā)一個(gè)桌面應(yīng)用程序一樣開發(fā)Web的Ajax程序。這使得在嵌入式系統(tǒng)上用一套代碼實(shí)現(xiàn)遠(yuǎn)程和本地操作界面成為了很容易的一件事。但是在嵌入式設(shè)備上運(yùn)行B-S結(jié)構(gòu)的應(yīng)用,性能上是一個(gè)很大的挑戰(zhàn)。同時(shí),瀏覽器兼容方面的問題也很嚴(yán)重,GWT目前的版本還不夠完善。事實(shí)證明,嵌入式的遠(yuǎn)程控制方案還是要采用Activex,VNC或者其他的方案。不要有太多的層次分層結(jié)構(gòu)有利于清晰的劃分系統(tǒng)職責(zé),實(shí)現(xiàn)系統(tǒng)的解耦,但是每多一個(gè)層次,就意味著性能的一次損失。尤其是當(dāng)層和層之間需要傳遞大量數(shù)據(jù)的時(shí)候。對(duì)嵌入式系統(tǒng)而言,在采用分層結(jié)構(gòu)時(shí)要控制層次數(shù)量,并且盡量不要傳遞大量數(shù)據(jù),尤其是在不同進(jìn)程的層次之間。如果一定要傳遞數(shù)據(jù),要避免大量的數(shù)據(jù)格式轉(zhuǎn)換,如XML到二進(jìn)制,C++結(jié)構(gòu)到Python結(jié)構(gòu)。嵌入式系統(tǒng)能力有限,一定要將有限的能力用在系統(tǒng)的核心功能上。存儲(chǔ)設(shè)備易損壞,速度較慢受體積和成本的限制,大部分的嵌入式設(shè)備使用諸如CompactFlash,SD,miniSD,MMC等作為存儲(chǔ)設(shè)備。這些設(shè)備雖然有著不擔(dān)心機(jī)械運(yùn)動(dòng)損壞的優(yōu)點(diǎn),但是其本身的使用壽命都比較短暫。比如,CF卡一般只能寫100萬次。而SD更短,只有10萬次。對(duì)于像數(shù)碼相機(jī)這樣的應(yīng)用,也許是足夠的。但是對(duì)于需要頻繁擦寫磁盤的應(yīng)用,比如歷史數(shù)據(jù)庫,磁盤的損壞問題會(huì)很快顯現(xiàn)。比如有一個(gè)應(yīng)用式每天向CF卡上寫一個(gè)16M的文件,文件系統(tǒng)是FAT16,每簇大小是2K,那么寫完這個(gè)16M的文件,分區(qū)表需要寫8192次,于是一個(gè)100萬次壽命的CF實(shí)際能夠工作的時(shí)間是1000000/8192=122天。而損壞的時(shí)候,CF卡的其他絕大部分地方的使用次數(shù)不過萬分之一。除了因?yàn)殪o態(tài)的文件分區(qū)表等區(qū)塊被頻繁的讀寫而提前損壞,一些嵌入式設(shè)備還要面對(duì)直接斷電的挑戰(zhàn),這會(huì)在存儲(chǔ)設(shè)備上產(chǎn)生不完整的數(shù)據(jù)。損耗均衡損耗均衡的基本思路是平均地使用存儲(chǔ)器上的各個(gè)區(qū)塊。需要維護(hù)一張存儲(chǔ)器區(qū)塊使用情況的表,這個(gè)表包括區(qū)塊的偏移位置,當(dāng)前是否可用,以及已經(jīng)擦寫地次數(shù)。當(dāng)有新的擦寫請(qǐng)求的時(shí)候,根據(jù)以下原則選擇區(qū)塊:盡量連續(xù)擦寫次數(shù)最少即使是更新已經(jīng)存在的數(shù)據(jù),也會(huì)使用以上原則分配新的區(qū)塊。同樣,這張表的存放位置也不能是固定不變的,否則這張表所占據(jù)的區(qū)塊就會(huì)最先損壞。當(dāng)要更新這張表的時(shí)候,同樣要使用以上算法分配區(qū)塊。如果存儲(chǔ)器上有大量的靜態(tài)數(shù)據(jù),那么上述算法就只能針對(duì)剩下的空間生效,這種情況下還要實(shí)現(xiàn)對(duì)這些靜態(tài)數(shù)據(jù)的搬運(yùn)的算法。但是這種算法會(huì)降低寫操作的性能,也增加了算法的復(fù)雜度。一般都只使用動(dòng)態(tài)均衡算法。目前比較成熟的損耗均衡的文件系統(tǒng)有JFFS2,和YAFFS。也有另一種思路就是在FAT16等傳統(tǒng)文件系統(tǒng)上實(shí)現(xiàn)損耗均衡,只要事先分配一塊足夠大的文件,在文件內(nèi)部實(shí)現(xiàn)損耗均衡算法。不過必須修改FAT16的代碼,關(guān)閉對(duì)最后修改時(shí)間的更新。現(xiàn)在的CF卡和SD卡有的已經(jīng)在內(nèi)部實(shí)現(xiàn)了損耗均衡,這種情況下就不需要軟件實(shí)現(xiàn)了。錯(cuò)誤恢復(fù)如果在向存儲(chǔ)器寫數(shù)據(jù)的時(shí)候發(fā)生斷電或者被拔出,那么所寫的區(qū)域的數(shù)據(jù)就處于未知的狀態(tài)。在一些應(yīng)用中,這會(huì)導(dǎo)致不完整的文件,而在另一些應(yīng)用中,則會(huì)導(dǎo)致系統(tǒng)失敗。所以對(duì)這類錯(cuò)誤的恢復(fù)也是嵌入式軟件設(shè)計(jì)必須考慮的。常用的思路有兩種:日志型的文件系統(tǒng)這種文件系統(tǒng)并不是直接存儲(chǔ)數(shù)據(jù),而是一條條的日志,所以當(dāng)發(fā)生斷電的時(shí)候,總可以恢復(fù)到之前的狀態(tài)。這類文件系統(tǒng)的代表如ext3。雙備份雙備份的思路更簡單,所有的數(shù)據(jù)都寫兩份。每次交替使用。文件分區(qū)表也必須是雙備份的。假設(shè)有數(shù)據(jù)塊A,A1是他的備份塊,在初始時(shí)刻和A的內(nèi)容是一致的。在分區(qū)表中,F(xiàn)指向數(shù)據(jù)塊A,F(xiàn)1是他的備份塊。當(dāng)修改文件時(shí),首先修改數(shù)據(jù)塊A1的內(nèi)容,如果此時(shí)斷電,A1的內(nèi)容錯(cuò)誤,但因?yàn)镕指向的是完好的A,所以數(shù)據(jù)沒有損壞。如果A1修改成功,則修改F1的內(nèi)容,如果此時(shí)斷電,因?yàn)镕是完好的,所以依然沒有問題。現(xiàn)在的Flash設(shè)備,有的已經(jīng)內(nèi)置錯(cuò)誤檢測(cè)和錯(cuò)誤校正技術(shù),可以保證在斷電時(shí)數(shù)據(jù)的完整。還有的包括自動(dòng)的動(dòng)態(tài)/靜態(tài)損耗均衡算法和壞塊處理,完全無須上層軟件額外對(duì)待,可以當(dāng)作硬盤使用。所以,硬件越發(fā)達(dá),軟件就會(huì)越可靠,技術(shù)不斷的進(jìn)步,將讓我們可以把更多的精力投入到軟件功能的本身,這是發(fā)展的趨勢(shì)。故障成本高昂嵌入式產(chǎn)品都是軟硬件一起銷售的給用戶的,所以這帶來了一個(gè)純軟件所不具備的問題,那就是當(dāng)產(chǎn)品發(fā)生故障時(shí),如果需要返廠才能修復(fù),則成本就很高。嵌入式設(shè)備常見有以下的幾類故障:數(shù)據(jù)故障。由于某些原因?qū)е聰?shù)據(jù)不能讀出或者不一致。比如斷電引起的數(shù)據(jù)庫錯(cuò)誤。軟件故障。軟件本身的缺陷,需要通過發(fā)布補(bǔ)丁程序或者新版本的軟件修正。系統(tǒng)故障。比如用戶下載了錯(cuò)誤的系統(tǒng)內(nèi)核,導(dǎo)致系統(tǒng)無法啟動(dòng)。硬件故障。這種故障只有返廠,不屬于我們的討論范圍。針對(duì)前三類故障,要盡可能保證客戶自己,或者現(xiàn)場(chǎng)技術(shù)人員就可以解決。從架構(gòu)的角度考慮,如下原則可以參考:使用具備錯(cuò)誤恢復(fù)能力的數(shù)據(jù)管理設(shè)計(jì)。當(dāng)數(shù)據(jù)發(fā)生錯(cuò)誤時(shí),用戶可以接受的處理依次是:錯(cuò)誤被糾正,所有數(shù)據(jù)有效錯(cuò)誤發(fā)生時(shí)的數(shù)據(jù)(可能不完整)丟失,之前的數(shù)據(jù)有效。所有數(shù)據(jù)丟失數(shù)據(jù)引擎崩潰無法繼續(xù)工作一般而言,滿足第二個(gè)條件即可。(日志,事務(wù),備份,錯(cuò)誤識(shí)別)將應(yīng)用程序和系統(tǒng)分離。應(yīng)用程序應(yīng)該放置在可插拔的Flash卡上,可以通過讀卡器進(jìn)行文件復(fù)制升級(jí)。非必要的情況不要使用專用應(yīng)用軟件來升級(jí)應(yīng)用程序。要有“安全模式”。即當(dāng)主系統(tǒng)被損壞后,設(shè)備依然可以啟動(dòng),重新升級(jí)系統(tǒng)。常見的uboot可以保證這一點(diǎn),在系統(tǒng)損壞后,可以進(jìn)入uboot通過tftp重新升級(jí)。軟件框架在桌面系統(tǒng)和網(wǎng)絡(luò)系統(tǒng)上,框架是普遍應(yīng)用的,比如著名的ACE,MFC,RubyOnRails等。而在嵌入式系統(tǒng)中,框架則是很少使用的。究其原因,大概是認(rèn)為嵌入式系統(tǒng)簡單,沒有重復(fù)性,過于注重功能的實(shí)現(xiàn)和性能的優(yōu)化。在前言中我們已經(jīng)提到,現(xiàn)在的嵌入式發(fā)展趨勢(shì)是向著復(fù)雜化,大型化,系列化發(fā)展的。所以,在嵌入式下設(shè)計(jì)軟件框架也是很有必要,也很有價(jià)值的。嵌入式軟件架構(gòu)面臨的問題前面我們講到,嵌入式系統(tǒng)軟件架構(gòu)所面臨的一些問題,其中很重要的一點(diǎn)是,對(duì)硬件的依賴和硬件相關(guān)軟件的復(fù)雜性。還包括嵌入式軟件在穩(wěn)定性和內(nèi)存占用等方面的苛刻要求。如果團(tuán)隊(duì)中的每個(gè)人都是這些方面高手的話,也許有可能開發(fā)出高質(zhì)量的軟件,但事實(shí)是一個(gè)團(tuán)隊(duì)中可能只有一兩個(gè)資深人員,其他大部分都是初級(jí)工程師。人人都去和硬件打交道,都負(fù)責(zé)穩(wěn)定性,性能等等指標(biāo)的話,是很難保證最終產(chǎn)品質(zhì)量的。如果組件團(tuán)隊(duì)時(shí)都是精通硬件等底層技術(shù)的人才,又很難設(shè)計(jì)出在可用性,擴(kuò)展性等方面出色的軟件。術(shù)業(yè)有專攻,架構(gòu)師的選擇決定著團(tuán)隊(duì)的組成方式。同時(shí),嵌入式軟件開發(fā)雖然復(fù)雜,但是也存在大量的重用的可能性。如何重用,又如何應(yīng)對(duì)將來的變更?所以,如何將復(fù)雜性對(duì)大多數(shù)人屏蔽,如何將關(guān)注點(diǎn)分離,如何保證系統(tǒng)的關(guān)鍵非功能指標(biāo),是嵌入式軟件架構(gòu)設(shè)計(jì)師應(yīng)該解決的問題。一種可能的解決方案就是軟件框架。什么是框架框架是在一個(gè)給定的問題領(lǐng)域內(nèi),為了重用和應(yīng)對(duì)未來需求變化而設(shè)計(jì)的軟件半成品??蚣軓?qiáng)調(diào)對(duì)特定領(lǐng)域的抽象,包含大量的專業(yè)領(lǐng)域知識(shí),以縮短軟件的開發(fā)周期,提高軟件質(zhì)量為目的。使用框架的二次開發(fā)者通過重寫子類或組裝對(duì)象的方式來實(shí)現(xiàn)特殊的功能。軟件復(fù)用的層次復(fù)用是在我們經(jīng)常談到的話題,“不要重復(fù)發(fā)明輪子”也是耳熟能詳?shù)慕錀l。不過對(duì)于復(fù)用的理解實(shí)際上是有很多個(gè)層次的。最基礎(chǔ)的復(fù)用是復(fù)制粘貼。某個(gè)功能以前曾經(jīng)實(shí)現(xiàn)過,再次需要的時(shí)候就復(fù)制過來,修改一下就可以使用。經(jīng)驗(yàn)豐富的程序員一般都會(huì)有自己的程序庫,這樣他們實(shí)現(xiàn)的時(shí)候就會(huì)比新的程序員快。復(fù)制粘貼的缺點(diǎn)是代碼沒有經(jīng)過抽象,往往并不完全的適用,所以需要進(jìn)行修改,經(jīng)過多次復(fù)用后,代碼將會(huì)變得混亂,難以理解。很多公司的產(chǎn)品都有這個(gè)問題,一個(gè)產(chǎn)品的代碼從另一個(gè)產(chǎn)品復(fù)制而來,修改一下就用,有時(shí)候甚至類名變量名都不改。按照“只有為復(fù)用設(shè)計(jì)的代碼才能真正復(fù)用”的標(biāo)準(zhǔn),這稱不上是復(fù)用,或者說是低水平的復(fù)用。更高級(jí)的復(fù)用是則是庫。這種功能需要對(duì)經(jīng)常使用的功能進(jìn)行抽象,提取出其中恒定不變的部分,以庫的形式提供給二次開發(fā)程序員使用。因?yàn)樵O(shè)計(jì)庫的時(shí)候不知道二次開發(fā)者會(huì)如何使用,所以對(duì)設(shè)計(jì)者有著很高的要求。這是使用最廣泛的一種復(fù)用,比如標(biāo)準(zhǔn)C庫,STL庫。現(xiàn)在非常流行的Python語言的重要優(yōu)勢(shì)之一就是其庫支持非常廣泛,相反C++一直缺少一個(gè)強(qiáng)大統(tǒng)一的庫支持,成為短板。在公司內(nèi)部的開發(fā)中總結(jié)常用功能并開發(fā)成庫是很有價(jià)值的,缺點(diǎn)是對(duì)庫的升級(jí)會(huì)影響到很多的產(chǎn)品,必須慎之又慎??蚣苁橇硪环N復(fù)用。和庫一樣,框架也是對(duì)系統(tǒng)中不變的部分進(jìn)行抽象并加以實(shí)現(xiàn),由二次開發(fā)者實(shí)現(xiàn)其他變化的部分。典型的框架和庫的最大的區(qū)別是,庫是靜態(tài)的,由二次開發(fā)者調(diào)用的;框架是活著的,它是主控者,二次開發(fā)者的代碼必須符合框架的設(shè)計(jì),由框架決定在何時(shí)調(diào)用。舉個(gè)例子,一個(gè)網(wǎng)絡(luò)應(yīng)用總是要涉及到連接的建立,數(shù)據(jù)收發(fā)和連接的關(guān)閉。以庫的形式提供是這樣的:conn=connect(host,port);if(conn.isvalid()){ data=conn.recv(); printf(data); conn.close();}框架則是這樣的:classmycomm:classconnect{ public:host();port();onconnected(); ondataarrived(unsignedchar*data,intlen); onclose();};框架會(huì)在“適當(dāng)”的時(shí)機(jī)創(chuàng)建mycomm對(duì)象,并查詢host和port,然后建立連接。在連接建立后,調(diào)用onconnected()接口,給二次開發(fā)者提供進(jìn)行處理的機(jī)會(huì)。當(dāng)數(shù)據(jù)到達(dá)的時(shí)候調(diào)用ondataarrived接口讓二次開發(fā)者處理。這是好萊塢原則,“不要來找我們,我們會(huì)去找你”。當(dāng)然,一個(gè)完整的框架通常也要提供各種庫供二次開發(fā)者使用。比如MFC提供了很多的庫,如CString,但本質(zhì)上它是一個(gè)框架。比如實(shí)現(xiàn)一個(gè)對(duì)話框的OnInitDialog接口,就是由框架規(guī)定的。針對(duì)高度特定領(lǐng)域的抽象和庫比較起來,框架是更針對(duì)特定領(lǐng)域的抽象。庫,比如C庫,是面向所有的應(yīng)用的。而框架相對(duì)來說則要狹窄的多。比如MFC提供的框架只適合于Windows平臺(tái)的桌面應(yīng)用程序開發(fā),ACE則是針對(duì)網(wǎng)絡(luò)應(yīng)用開發(fā)的框架,RubyOnRails是為快速開發(fā)web站點(diǎn)設(shè)計(jì)的。越是針對(duì)特定的領(lǐng)域,抽象就可以做的越強(qiáng),二次開發(fā)就可以越簡單,因?yàn)楣残缘臇|西越多。比如我們上面談到嵌入式系統(tǒng)軟件開發(fā)的諸多特點(diǎn),這就是特定領(lǐng)域的共性,就屬于可以抽象的部分。具體到實(shí)際的嵌入式應(yīng)用,又會(huì)有更多的共性可以抽象。框架的設(shè)計(jì)目的是總結(jié)特定領(lǐng)域的共性,以框架的方式實(shí)現(xiàn),并規(guī)定二次開發(fā)者的實(shí)現(xiàn)方式,從而簡化開發(fā)。相應(yīng)的,針對(duì)一個(gè)領(lǐng)域開發(fā)的框架就不能服務(wù)于另一個(gè)領(lǐng)域。對(duì)企業(yè)而言,框架是一種極好的積累知識(shí),降低成本的技術(shù)手段。解除耦合和應(yīng)對(duì)變化框架設(shè)計(jì)的一個(gè)重要目的就是應(yīng)對(duì)變化。應(yīng)對(duì)變化的本質(zhì)就是解耦。從架構(gòu)師的角度看,解耦可以分為三種:邏輯解耦。邏輯解耦是將邏輯上不同的模塊抽象并分離處理。如數(shù)據(jù)和界面的解耦。這也是我們最常做的解耦。知識(shí)解耦。知識(shí)解耦是通過設(shè)計(jì)讓掌握不同知識(shí)的人僅僅通過接口工作。典型的如測(cè)試工程師所掌握的專業(yè)知識(shí)和開發(fā)工程師所掌握的程序設(shè)計(jì)和實(shí)現(xiàn)的知識(shí)。傳統(tǒng)的測(cè)試腳本通常是將這二者合二為一的。所以要求測(cè)試工程師同時(shí)具備編程的能力。通過適當(dāng)?shù)姆绞剑梢宰寽y(cè)試工程師以最簡單的方式實(shí)現(xiàn)他的測(cè)試用例,而開發(fā)人員編寫傳統(tǒng)的程序代碼來執(zhí)行這些用例。變與不變的解耦。這是框架的重要特征。框架通過對(duì)領(lǐng)域知識(shí)的分析,將共性,也就是不變的內(nèi)容固定下來,而將可能發(fā)生變化的部分交給二次開發(fā)者實(shí)現(xiàn)??蚣芸梢詫?shí)現(xiàn)和規(guī)定非功能性需求非功能性需求是指如性能,可靠性,可測(cè)試性,可移植性等。這些特性可以通過框架來實(shí)現(xiàn)。以下我們一一舉例。性能。對(duì)性能的優(yōu)化最忌諱的就是普遍優(yōu)化。系統(tǒng)的性能往往取決于一些特定的點(diǎn)。比如在嵌入式系統(tǒng)中,對(duì)存儲(chǔ)設(shè)備的訪問是比較慢的。如果開發(fā)者不注意這方面的問題,頻繁的讀寫存儲(chǔ)設(shè)備,就會(huì)造成性能下降。如果對(duì)存儲(chǔ)設(shè)備的讀寫由框架設(shè)計(jì),二次開發(fā)者只作為數(shù)據(jù)的提供和處理者,那么就可以在框架中對(duì)讀寫的頻率進(jìn)行調(diào)節(jié),從而達(dá)到優(yōu)化性能的目的。由于框架都是單獨(dú)開發(fā)的,完成后供廣泛使用,所以就有條件對(duì)關(guān)鍵的性能點(diǎn)進(jìn)行充分的優(yōu)化??煽啃?。以上面的網(wǎng)絡(luò)通訊程序?yàn)槔捎诳蚣茇?fù)責(zé)了連接的創(chuàng)建和管理,也處理了各種可能的網(wǎng)絡(luò)錯(cuò)誤,具體的實(shí)現(xiàn)者無須了解這方面的知識(shí),也無須實(shí)現(xiàn)這方面錯(cuò)誤處理的代碼,就可以保證整個(gè)系統(tǒng)在網(wǎng)絡(luò)通訊方面的可靠性。以框架的方式設(shè)計(jì)在可靠性方面的最大優(yōu)勢(shì)就是:二次開發(fā)的代碼是在框架的掌控之內(nèi)運(yùn)行的。一方面框架可以將容易出錯(cuò)的部分實(shí)現(xiàn),另一方面對(duì)二次開發(fā)的代碼產(chǎn)生的錯(cuò)誤也可以捕獲和處理。而庫則不能代替使用者處理錯(cuò)誤??蓽y(cè)試性??蓽y(cè)試性是軟件架構(gòu)需要考慮的一個(gè)重要方面。下面的章節(jié)會(huì)講到,軟件的可測(cè)試性是由優(yōu)良的設(shè)計(jì)來保證的。一方面,由于框架規(guī)定了二次開發(fā)的接口,所以可以迫使二次開發(fā)者開發(fā)出便于進(jìn)行單元測(cè)試的代碼。另一方面,框架也可以在系統(tǒng)測(cè)試的層面上提供易于實(shí)現(xiàn)自動(dòng)化測(cè)試和回歸測(cè)試的設(shè)計(jì),例如統(tǒng)一提供的TL1接口。可移植性。如果軟件的可移植性是軟件設(shè)計(jì)的目標(biāo),框架設(shè)計(jì)者可以在設(shè)計(jì)階段來保證這一點(diǎn)。一種方式是通過跨平臺(tái)的庫來屏蔽系統(tǒng)差異,另一種可能的方式更加極端,基于框架的二次開發(fā)可以是腳本化的。組態(tài)軟件是這方面的一個(gè)例子,在PC上組態(tài)的工程,也可以在嵌入式設(shè)備上運(yùn)行。一個(gè)框架設(shè)計(jì)的實(shí)例基本架構(gòu)軟件WinCE,ARM軟件WinCE,ARM芯片,觸摸屏,圖形化界面,Lua腳本界面硬件各種具體硬件,如ADSL測(cè)試芯片,VOIP測(cè)試芯片,可插拔替換固件pSOS實(shí)時(shí)系統(tǒng),MIPS芯片USB2.0對(duì)外提供XML,TL1接口功能特點(diǎn)上面是一個(gè)產(chǎn)品系列的架構(gòu)圖,其特點(diǎn)是硬件部分是模塊化的,可以隨時(shí)插拔。不同的硬件應(yīng)用于不同的通訊測(cè)試場(chǎng)合。比如光通訊測(cè)試,xDSL測(cè)試,CableModem測(cè)試等等。針對(duì)不同的硬件,需要開發(fā)不同的固件和軟件。固件層的功能主要是通過USB接口接收來自軟件的指令,并讀寫相應(yīng)的硬件接口,再進(jìn)行一些計(jì)算后,將結(jié)果返回給軟件。軟件運(yùn)行在WinCE平臺(tái),除了提供一個(gè)觸摸式的圖形化界面外,還對(duì)外提供基于XML(SOAP)接口和TL1接口。為了實(shí)現(xiàn)自動(dòng)化測(cè)試,還提供了基于Lua的腳本語言接口。整個(gè)產(chǎn)品系列有幾十個(gè)不同的硬件模塊,相應(yīng)的需要開發(fā)幾十套軟件。這些軟件雖然服務(wù)于不同的硬件,但是彼此之間有著高度的相似性。所以,選擇先開發(fā)一個(gè)框架,再基于框架開發(fā)具體的模塊軟件成了最優(yōu)的選擇。分析軟件部分的結(jié)構(gòu)分析如下:協(xié)議層協(xié)議層圖形界面TL1Server內(nèi)存數(shù)據(jù)庫通訊層SOAPServerTerm/script硬件通訊層任務(wù)1任務(wù)2任務(wù)…任務(wù)n硬件抽象層(FPGA/IOBoard…..)派發(fā)器協(xié)議層視圖SOAPClientSOAPClient系統(tǒng)分為軟件,固件和硬件三大塊。軟件和固件運(yùn)行在兩塊獨(dú)立的板子上,有各自的處理器和操作系統(tǒng)。硬件則插在固件所在的板子上,是可以替換的。軟件和固件其實(shí)都是軟件,下面我們分別分析。軟件軟件的主要工作是提供各種用戶界面。包括本地圖形化界面,SOAP訪問界面,TL1訪問界面。整個(gè)軟件部分分為五大部分:通訊層協(xié)議層圖形界面SOAP服務(wù)器TL1服務(wù)器通訊層要屏蔽用戶對(duì)具體通信介質(zhì)和協(xié)議的了解,無論是USB還是socket,對(duì)上層都不產(chǎn)生影響。通訊層負(fù)責(zé)提供可靠的通訊服務(wù)和適當(dāng)?shù)腻e(cuò)誤處理。通過配置文件,用戶可以改變所使用的通訊層。協(xié)議層的目的是將數(shù)據(jù)進(jìn)行編碼和解碼。編碼的產(chǎn)生物是可以在通訊層發(fā)送的流,按照嵌入式軟件的特點(diǎn),我們選擇二進(jìn)制作為流的格式。解碼的產(chǎn)生物是多種的,既有供界面使用的CStruct,也可以是XML數(shù)據(jù),還可以是Lua的數(shù)據(jù)結(jié)構(gòu)(tablegt)。如果需要,還可以產(chǎn)生JSON,TL1,Python數(shù)據(jù),TCL數(shù)據(jù)等等。這一層在框架中是通過機(jī)器自動(dòng)生成的,我們后面會(huì)講到。內(nèi)存數(shù)據(jù)庫,SOAPServer和TL1Server都是協(xié)議層的用戶。圖形界面通過讀寫內(nèi)存數(shù)據(jù)庫和底層通訊。圖形界面是框架設(shè)計(jì)的重點(diǎn)之一,原因是這里工作量最大,重復(fù)而無聊的工作最多。讓我們分析一下在圖形界面開發(fā)工作中最主要的事情是什么。收集用戶輸入的數(shù)據(jù)和命令將數(shù)據(jù)和命令發(fā)給底層接收底層反饋將數(shù)據(jù)顯示在界面上同時(shí)有一些庫用來進(jìn)一步簡化開發(fā): 這是一個(gè)簡化的例子,但是很好的說明了框架的特點(diǎn):客戶代碼必須按照規(guī)定的接口實(shí)現(xiàn)框架在適當(dāng)?shù)臅r(shí)候調(diào)用客戶實(shí)現(xiàn)的接口每個(gè)接口都被設(shè)計(jì)為只完成特定的單一功能將各個(gè)步驟有機(jī)的串起來是框架的事,二次開發(fā)者不知道,也無須知道。通常都要有附帶的庫。固件固件的主要工作是接受來自軟件的命令,驅(qū)動(dòng)硬件工作;獲取硬件的狀態(tài),進(jìn)行一定的計(jì)算后返回給軟件。早期的固件是很薄的一層,因?yàn)榻^大部分工作是由硬件完成的,固件只起到一個(gè)中轉(zhuǎn)通訊的作用。隨著時(shí)代發(fā)展,現(xiàn)在的固件開始承擔(dān)越來越多原來由硬件完成的工作。整個(gè)固件部分分為五大部分:硬件抽象層,提供對(duì)硬件的訪問接口互相獨(dú)立的任務(wù)群任務(wù)/消息派發(fā)器協(xié)議層通訊層針對(duì)不同的設(shè)備,工作量集中在硬件抽象層和任務(wù)群上。硬件抽象層是以庫的形式提供的,由對(duì)硬件最熟悉,經(jīng)驗(yàn)最豐富的工程師來實(shí)現(xiàn)。任務(wù)群則由一系列的任務(wù)組成,他們分別代表不同的業(yè)務(wù)應(yīng)用。比如測(cè)量誤碼率。這部分由相對(duì)經(jīng)驗(yàn)較少的工程師來實(shí)現(xiàn),他們的主要工作是實(shí)現(xiàn)規(guī)定的接口,按照標(biāo)準(zhǔn)化文檔定義的方式實(shí)現(xiàn)算法。任務(wù)定義了如下接口,由具體開發(fā)者來實(shí)現(xiàn):OnInit();OnRegisterMessage();OnMessageArrive();Run();OnResultReport();框架的代碼流程如下:(偽代碼)CTask*task=newCBertTask();task->OnInit();task->OnRegisterMessage();while(TRUE){ task->OnMessageArrive(); task->Run(); task->OnResultReport();}deletetask;task=NULL;這樣,具體任務(wù)的實(shí)現(xiàn)者所關(guān)注的最重要的事情就是實(shí)現(xiàn)這幾個(gè)接口。其他如硬件的初始化,消息的收發(fā),編碼解碼,結(jié)果的上報(bào)等等事情都由框架進(jìn)行了處理。避免了每個(gè)工程師都必須處理從上到下的所有方面。并且這樣的任務(wù)代碼還有很高的重用性,比如是在以太網(wǎng)上還是在CableModem上實(shí)現(xiàn)PING的算法都是一樣的。實(shí)際效果在實(shí)際項(xiàng)目中,框架大大降低了開發(fā)難度。對(duì)軟件部分尤其明顯,由實(shí)習(xí)生即可完成高質(zhì)量的界面開發(fā),開發(fā)周期縮短50%以上。產(chǎn)品質(zhì)量大大提升。對(duì)固件部分的貢獻(xiàn)在于降低了對(duì)精通底層硬件的工程師的需要,一般的工程師熟知測(cè)量算法即可。同時(shí),框架的存在保證了性能,穩(wěn)定和可測(cè)試性等要素??蚣茉O(shè)計(jì)中的常用模式模板方法模式模板方法模式是框架中最常用的設(shè)計(jì)模式。其根本的思路是將算法由框架固定,而將算法中具體的操作交給二次開發(fā)者實(shí)現(xiàn)。例如一個(gè)設(shè)備初始化的邏輯,框架代碼如下:TBoolCBaseDevice::Init(){if(DownloadFPGA()!=KErrNone){ LOG(LOG_ERROR,_L(“DownloadFPGAfail”)); returnEFalse;} if(InitKeyPad()!=KerrNone) {LOG(LOG_ERROR,_L(“Initializekeypadfail”)); returnEFalse;}returnETrue;}DownloadFPGA和InitKeyPad都是CBaseDevice定義的虛函數(shù),二次開發(fā)者創(chuàng)建一個(gè)繼承于CBaseDevice的子類,具體來實(shí)現(xiàn)這兩個(gè)接口。框架定義了調(diào)用的次序和錯(cuò)誤的處理方式,二次開發(fā)者無須關(guān)心,也無權(quán)決定。創(chuàng)建型模式由于框架通常都涉及到各種不同子類對(duì)象的創(chuàng)建,創(chuàng)建型模式是經(jīng)常使用的。例如一個(gè)繪圖軟件的框架,有一個(gè)基類定義了圖形對(duì)象的接口,基于它可以派生出橢圓,矩形,直線各種子類。當(dāng)用戶繪制一個(gè)圖形時(shí),框架就要實(shí)例化該子類。這時(shí)候可以用工廠方法,原型方法等等。classCDrawObj{public:virtualintDrawObjTypeID()=0;virtualIconGetToolBarIcon()=0;virtualvoidDraw(Rectrect)=0;virtualCDrawObj*Clone()=0;};消息訂閱模式消息訂閱模式是最常用的分離數(shù)據(jù)和界面的方式。界面開發(fā)者只需要注冊(cè)需要的數(shù)據(jù),當(dāng)數(shù)據(jù)變化時(shí)框架就會(huì)將數(shù)據(jù)“推”到界面。界面開發(fā)者可以無須關(guān)注數(shù)據(jù)的來源和內(nèi)部組織形式。消息訂閱模式最常見的問題是同步模式下如何處理重入和超時(shí)。作為框架設(shè)計(jì)者,一定要考慮好這個(gè)問題。所謂重入,是二次開發(fā)者在消息的回調(diào)函數(shù)中執(zhí)行訂閱/取消訂閱的操作,這會(huì)破壞消息訂閱的機(jī)制。所謂超時(shí)是指二次開發(fā)者的消息回調(diào)函數(shù)處理時(shí)間過長,導(dǎo)致其他消息無法響應(yīng)。最簡單的辦法是使用異步模式,讓訂閱者和數(shù)據(jù)發(fā)布者在獨(dú)立進(jìn)程/線程中運(yùn)行。如果不具備此條件,則必須作為框架的重要約定,禁止二次開發(fā)者產(chǎn)生此類問題。裝飾器模式裝飾器模式賦予了框架在后期增加功能的能力。框架定義裝飾器的抽象基類,而由具體的實(shí)現(xiàn)者實(shí)現(xiàn),動(dòng)態(tài)地添加到框架中。舉一個(gè)游戲中的例子,圖形繪制引擎是一個(gè)獨(dú)立的模塊,比如可以繪制人物的靜止,跑動(dòng)等圖像。如果策劃決定在游戲中增加一種叫“隱身衣”的道具,要求穿著此道具的玩家在屏幕上顯示的是若有若無的半透明圖像。應(yīng)該如何設(shè)計(jì)圖像引擎來適應(yīng)后期的游戲升級(jí)呢?當(dāng)隱身衣被裝備后,就向圖像引擎添加一個(gè)過濾器。這是個(gè)極度簡化的例子,實(shí)際的游戲引擎要比這個(gè)復(fù)雜。裝飾器模式還常見用于數(shù)據(jù)的前置和后置處理上??蚣艿娜秉c(diǎn)一個(gè)好的框架可以大大提高產(chǎn)品的開發(fā)效率和質(zhì)量,但也有它的缺點(diǎn)。框架一般都比較復(fù)雜,設(shè)計(jì)和實(shí)現(xiàn)一個(gè)好的框架需要相當(dāng)?shù)臅r(shí)間。所以,一般只有在框架可以被多次反復(fù)應(yīng)用的時(shí)候適合,這時(shí)候,前提投入的成本會(huì)得到豐厚的回報(bào)。框架規(guī)定了一系列的接口和規(guī)則,這雖然簡化了二次開發(fā)工作,但同時(shí)也要求二次開發(fā)者必須記住很多規(guī)定,如果違反了這些規(guī)定,就不能正常工作。但是由于框架屏蔽了大量的領(lǐng)域細(xì)節(jié),相對(duì)而言,其學(xué)習(xí)成本還是大大降低了的??蚣艿纳?jí)對(duì)已有產(chǎn)品可能會(huì)造成嚴(yán)重的影響,導(dǎo)致需要完整的回歸測(cè)試。對(duì)這個(gè)問題有兩個(gè)辦法。第一是對(duì)框架本身進(jìn)行嚴(yán)格的測(cè)試,有必要建立完善的單元測(cè)試庫,同時(shí)開發(fā)示例項(xiàng)目,用來測(cè)試框架的所有功能。第二則是使用靜態(tài)鏈接,讓已有產(chǎn)品不輕易跟隨升級(jí)。當(dāng)然,如果已有產(chǎn)品有較好的回歸測(cè)試手段,就更好。性能損失。由于框架對(duì)系統(tǒng)進(jìn)行了抽象,增加了系統(tǒng)的復(fù)雜性。諸如多態(tài)這樣的手段使用也會(huì)普遍的降低系統(tǒng)的性能。但是從整體上來看,框架可以保證系統(tǒng)的性能處于一個(gè)較高的水平。自動(dòng)代碼生成機(jī)器能做的事就不要讓人來做懶惰是程序員的美德,更是架構(gòu)師的美德。軟件開發(fā)的過程就是人告訴機(jī)器如何做事的過程。如果一件事情機(jī)器自己就可以做,那就不要讓人來做。因?yàn)闄C(jī)器不僅不知疲倦,而且絕不會(huì)犯錯(cuò)。我們的工作是讓客戶的工作自動(dòng)化,多想一點(diǎn),就能讓我們自己的工作也部分自動(dòng)化。極有耐心的程序員是好的,也是不好的。經(jīng)過良好設(shè)計(jì)的系統(tǒng),往往會(huì)出現(xiàn)很多高度類似而且具有很強(qiáng)規(guī)律的代碼。未經(jīng)良好設(shè)計(jì)的系統(tǒng)則可能對(duì)同一類功能產(chǎn)生很多不同的實(shí)現(xiàn)。前面關(guān)于框架設(shè)計(jì)的部分已經(jīng)證明了這一點(diǎn)。有時(shí)候,我們更進(jìn)一步,分析出這些相似代碼之中的規(guī)律,用格式化的數(shù)據(jù)來描述這些功能,而由機(jī)器來產(chǎn)生代碼。舉例消息的編碼和解碼上面關(guān)于框架的實(shí)例中,可以看到消息編解碼的部分已經(jīng)被獨(dú)立出來,和其他部分沒有耦合。加上他本身的特點(diǎn),非常適合進(jìn)一步將其“規(guī)則化”,用機(jī)器產(chǎn)生代碼。編碼,就是把數(shù)據(jù)結(jié)構(gòu)流化;解碼反之。以編碼為例,代碼無非是這樣的:(二進(jìn)制協(xié)議)stream<<a.i;stream<<a.j;stream<<a.object;(為了簡化,這里假設(shè)已經(jīng)設(shè)計(jì)了一個(gè)流對(duì)象,可以流化各種數(shù)據(jù)類型,并且已經(jīng)處理了諸如字節(jié)序轉(zhuǎn)換等問題。)最后我們得到一個(gè)stream。大家是否已經(jīng)習(xí)慣了寫這種代碼?但是這樣的代碼不能體現(xiàn)工程師任何的創(chuàng)造性,因?yàn)槲覀冊(cè)缫呀?jīng)知道有i,有j,還有一個(gè)object,為什么還要自己敲入這些代碼呢?如果我們分析一下a的定義,是不是就可以自動(dòng)產(chǎn)生這樣的代碼呢?structdataA{ inti; intj; structdataBobject;};只需要一個(gè)簡單的語義分析器解析這段代碼,得到一棵關(guān)于數(shù)據(jù)類型的樹,就可以輕易的產(chǎn)生流化的代碼。這樣的分析器用Python等字符串處理能力強(qiáng)的語言不過兩百行左右。關(guān)于數(shù)據(jù)類型的樹類似下圖:structdataAstructdataAi:intobject:structdataBi:intstructdataBname:stringids:intarrayTypes只要遍歷這棵樹,就可以生成所有數(shù)據(jù)結(jié)構(gòu)的流化代碼。在上一個(gè)框架所舉例的項(xiàng)目中,為一個(gè)硬件模塊自動(dòng)產(chǎn)生的消息編碼解碼器代碼量高達(dá)三萬行,幾乎相當(dāng)于一個(gè)小軟件。由于是自動(dòng)產(chǎn)生,沒有任何錯(cuò)誤,為上層提供了高可靠性。還可以用XML或者其他的格式定義數(shù)據(jù)結(jié)構(gòu),從而產(chǎn)生自動(dòng)代碼。根據(jù)需要,C++/Java/Python,任何類型的都可以。如果希望提供強(qiáng)檢查,可以使用XSD來定義數(shù)據(jù)結(jié)構(gòu)。有一個(gè)商業(yè)化的產(chǎn)品,xBinder,很貴,很難用,還不如自己開發(fā)。(為什么難用?因?yàn)樗ㄓ?。除了編碼為二進(jìn)制格式,還可以編碼為任何你需要的格式。我們知道二進(jìn)制格式雖然效率很高,但是太難調(diào)試(當(dāng)然有些人看內(nèi)存里的十六進(jìn)制還是很快的),所以我們可以在編碼成二進(jìn)制的同時(shí),還生成編碼為其他可閱讀的格式的代碼,比如XML。這樣,通訊使用二進(jìn)制,而調(diào)試使用XML,兩全其美。產(chǎn)生二進(jìn)制的代碼大概是這樣的:Xmlbuilder.addelement(“i”,a.i);Xmlbuilder.addelement(“j”,a.j);Xmlbuilder.addelement(“object”,a.object);同樣也很適合機(jī)器產(chǎn)生。同樣的思路可以用來讓軟件內(nèi)嵌腳本支持。這里不多說了。(內(nèi)嵌腳本支持最大的問題是在C/C++和腳本之間交換數(shù)據(jù),也是針對(duì)數(shù)據(jù)類型的大量相似代碼。)最近Google發(fā)布了它的protocolbuffer,就是這樣的思路。目前支持C++/Python,估計(jì)很快會(huì)支持更多的語言,大家可以關(guān)注。以后就不要再手寫編碼解碼器了。GUI代碼上面的框架設(shè)計(jì)部分,我們說到框架對(duì)界面數(shù)據(jù)收集和界面更新無能為力,只能抽象出接口,由程序員具體實(shí)現(xiàn)。但是讓我們看看這些界面程序員做的事情吧。(代碼經(jīng)過簡化,可以看作偽代碼)。voidonDataArrive(CDataBinder&data){ m_biterror.setText(“%d”,data.biterror); m_signallevel.setText(“%d”,data.signallevel”); m_latency.setText(“%d”,data.latency”);}VoidonCollectData(CDataBinder&data){ data.biterror=atoi(m_biterror.getText());data.signallevel=atoi(m_signallevel.getText());data.latency=atoi(m_latency.getText());}這樣的代碼很有趣嗎?想想我們可以怎么做?(XML描述界面,問題是對(duì)于復(fù)雜邏輯很難)小結(jié)由此可見,在軟件架構(gòu)的過程中,首先要遵循一般性的原則,盡量將系統(tǒng)各個(gè)功能部分獨(dú)立出來,實(shí)現(xiàn)高內(nèi)聚低耦合,進(jìn)而發(fā)現(xiàn)系統(tǒng)存在的高度重復(fù),規(guī)律性很強(qiáng)的代碼,進(jìn)一步將他們規(guī)則化,形式化,最后用機(jī)器來產(chǎn)生這些代碼。目前這方面最成功的應(yīng)用就是消息的編解碼。對(duì)界面代碼的自動(dòng)化生成有一定局限,但也可以應(yīng)用。大家在自己的工作中要擅于發(fā)現(xiàn)這樣的可能,減少工作量,提高工作效率。GoogleProtocolBufferGoogle剛剛發(fā)布的ProtocolBuffer是使用代碼自動(dòng)生成的一個(gè)典范。Protocolbuffersareaflexible,efficient,automatedmechanismforserializingstructureddata–thinkXML,butsmaller,faster,andsimpler.Youdefinehowyouwantyourdatatobestructuredonce,thenyoucanusespecialgeneratedsourcecodetoeasilywriteandreadyourstructureddatatoandfromavarietyofdatastreamsandusingavarietyoflanguages.Youcanevenupdateyourdatastructurewithoutbreakingdeployedprogramsthatarecompiledagainstthe"old"format.你要做的首先是定義消息的格式,Google指定了它的格式:messagePerson{requiredstringname=1;requiredint32id=2;optionalstringemail=3;enumPhoneType{MOBILE=0;HOME=1;WORK=2;}messagePhoneNumber{requiredstringnumber=1;optionalPhoneTypetype=2[default=HOME];}repeatedPhoneNumberphone=4;}Onceyou'vedefinedyourmessages,youruntheprotocolbuffercompilerforyourapplication'slanguageonyour.protofiletogeneratedataaccessclasses.Theseprovidesimpleaccessorsforeachfield(likequery()andset_query())aswellasmethodstoserialize/parsethewholestructureto/fromrawbytes–so,forinstance,ifyourchosenlanguageisC++,runningthecompilerontheaboveexamplewillgenerateaclasscalledPerson.Youcanthenusethisclassinyourapplicationtopopulate,serialize,andretrievePersonprotocolbuffermessages.Youmightthenwritesomecodelikethis:Personperson;

person.set_name("JohnDoe");

person.set_id(1234);

person.set_email("jdoe@");

fstreamoutput("myfile",ios::out|ios::binary);

person.SerializeToOstream(&output);Then,lateron,youcouldreadyourmessagebackin:fstreaminput("myfile",ios::in|ios::binary);

Personperson;

person.ParseFromIstream(&input);

cout<<"Name:"<<()<<endl;

cout<<"E-mail:"<<person.email()<<endl;ProtocolBuffer的編碼格式是二進(jìn)制的,同時(shí)也提供可讀的文本格式。效率高,體積小,上下兼容。目前支持Java,Python和C++,很快會(huì)支持更多的語言。面向語言編程(LOP)從自動(dòng)化代碼生成更進(jìn)一步面向語言編程的通俗定義是:將特定領(lǐng)域的知識(shí)融合到一種專用的計(jì)算機(jī)語言當(dāng)中,從而提高人與計(jì)算機(jī)交流的效率。自動(dòng)化代碼生成其實(shí)就是面向語言編程。語言不等于是編程語言,可以是圖,也可以是表,任何可以建立人和機(jī)器之間交流渠道的都是計(jì)算機(jī)語言。軟件開發(fā)歷史上的一次生產(chǎn)率的飛躍是高級(jí)語言的發(fā)明。它讓我們以更簡潔的方式實(shí)現(xiàn)更復(fù)雜的功能。但是高級(jí)語言也有它的缺點(diǎn),那就是從問題領(lǐng)域到程序指令的過程很復(fù)雜。因?yàn)楦呒?jí)語言是為通用目的而設(shè)計(jì)的,所以離問題領(lǐng)域很遠(yuǎn)。舉例來說,要做一個(gè)圖形界面,我可以跟另一個(gè)工程師說:這里放一個(gè)按鈕,那邊放一個(gè)輸入框,當(dāng)按下按鈕的時(shí)候,就在輸入框里顯示HelloWorld。我甚至可以隨手給他畫出來。對(duì)于我和他直接的交流而言,這已經(jīng)足夠了,5分鐘。但是要讓轉(zhuǎn)變?yōu)橛?jì)算機(jī)能夠理解的語言,需要多久?如果是匯編語言?(告訴計(jì)算機(jī)如何操作寄存器和內(nèi)存)如果是C++?(告訴計(jì)算機(jī)如何在屏幕上繪圖,如果響應(yīng)鼠標(biāo)鍵盤消息)如果有一個(gè)不錯(cuò)的圖形界面庫?(告訴計(jì)算機(jī)創(chuàng)建Button,Label對(duì)象,管理這些對(duì)象,放置這些對(duì)象,處理消息)如果有一個(gè)不錯(cuò)的開發(fā)框架+IDE?(用WYSIWYG工具繪制,設(shè)計(jì)類,類的成員變量,編寫消息響應(yīng)函數(shù))如果有一門專門做圖形界面開發(fā)的語言?可以是這樣的:Labell{Text=””}Buttonb{Text=”ok”,action=l.Text=”helloworld”}通用的計(jì)算機(jī)語言是基于變量,類,分支,循環(huán),鏈表,消息這些概念的。這些概念離問題本身有著遙遠(yuǎn)的距離,而且表達(dá)能力非常有限。自然語言表達(dá)能力很強(qiáng),但是歧義和冗余太多,無法格式化標(biāo)準(zhǔn)化。傳統(tǒng)的思想告訴我們:計(jì)算機(jī)語言就是一條條的指令,編程就是寫下這些指令。而面向語言編程的思想是,用盡量貼近問題,貼近人的思維的辦法來描述問題,從而降低從人的思想到計(jì)算機(jī)軟件轉(zhuǎn)換的難度。舉一個(gè)游戲開發(fā)的例子?,F(xiàn)在的網(wǎng)絡(luò)游戲普遍的采用了C++或者C開發(fā)游戲引擎。而具體的游戲內(nèi)容,則是由一系列二次開發(fā)工具和語言完成的。地圖編輯器就是一種面向游戲的語言。Lua或者類似的腳本則被嵌入到游戲內(nèi)部,用來編寫武器,技能,任務(wù)等等。Lua本身不具備獨(dú)立開發(fā)應(yīng)用程序的能力,然而游戲引擎的設(shè)計(jì)者通過給Lua提供一系列的,各種層次上的接口,將領(lǐng)域知識(shí)密集的賦予了腳本,從而大大提高了游戲二次開發(fā)的效率。網(wǎng)絡(luò)游戲的鼻祖MUD則是設(shè)計(jì)了LPC來作為游戲的開發(fā)語言。MUD的引擎MudOS和LPC之間的關(guān)系如圖:用LPC創(chuàng)建一個(gè)NPC的代碼類似如下:inheritNPC;voidcreate(){set_name("菜花蛇",({"caihuashe","she"}));set("race","野獸");set("age",1);set("long","一只青幽幽的菜花蛇,頭部呈橢圓形。\n");set("attitude","peaceful");set("str",15);set("cor",16);set("limbs",({"頭部","身體","七寸","尾巴"}));set("verbs",({"bite"}));set("combat_exp",100+random(50));set_temp("apply/attack",7);set_temp("apply/damage",4);set_temp("apply/defence",6);set_temp("apply/armor",5);setup();}voiddie(){objectob;message_vision("$N抽搐兩下,$N死了。\n",this_object());ob=new(__DIR__"obj/sherou");ob->move(environment(this_object()));destruct(this_object());}LPC培養(yǎng)了一大批業(yè)余游戲開發(fā)者,甚至成為很多人進(jìn)入IT行業(yè)的起點(diǎn)。原因就是它簡單,易理解,100%為游戲開發(fā)設(shè)計(jì)。這就是LOP的魅力。優(yōu)勢(shì)和劣勢(shì)LOP最重要的優(yōu)點(diǎn)是將領(lǐng)域知識(shí)固化到語言中,從而:提高開發(fā)效率。優(yōu)化團(tuán)隊(duì)結(jié)構(gòu),降低交流成本,領(lǐng)域?qū)<液统绦騿T可以更好的合作。降低耦合,易于維護(hù)。其次,由于LOP不是通用語言,所涉及的范圍就狹窄很多,所以:更容易得到穩(wěn)定的系統(tǒng)更容易移植相應(yīng)的,LOP也有它的劣勢(shì):LOP對(duì)領(lǐng)域知識(shí)抽象的要求比框架更高。開發(fā)一門新的語言本身的成本。幸好現(xiàn)在設(shè)計(jì)一門新的語言不算太難,還有Lua這樣的“專用二次開發(fā)”語言的支持。性能損失。不過相比開發(fā)成本的節(jié)約,在非性能核心部分使用LOP還是很值得的。在嵌入式系統(tǒng)中的應(yīng)用舉例,嵌入式設(shè)備的Web服務(wù)器。很多設(shè)備都提供Web服務(wù)用于配置,比如路由器,ADSL貓等等。這種設(shè)備所提供的web服務(wù)的典型用例是用戶填寫一些參數(shù),提交給Web服務(wù)器,Web服務(wù)器將這些參數(shù)寫入硬件,并將操作結(jié)果或者其他信息生成頁面返回給瀏覽器。由于典型的Apache,Mysql,PHP組合體積太大且不容易移植,通常嵌入式系統(tǒng)的Web服務(wù)都是用C/C++直接寫就的。從socket管理,http協(xié)議到具體操作硬件,生成頁面,都一體負(fù)責(zé)。然而對(duì)于功能復(fù)雜,Web界面要求較高的情況,用C來寫頁面效率就太低了。shttpd是一個(gè)小巧的web服務(wù)器,小巧到只有一個(gè).c文件,4000余行代碼。雖然體積很小,卻具備了最基本的功能,比如CGI。它既可以獨(dú)立運(yùn)行,也可以嵌入到其他的應(yīng)用程序當(dāng)中。shttpd在大多數(shù)平臺(tái)上都可以順利編譯、運(yùn)行。lua是一個(gè)小巧的腳本語言,專用于嵌入和擴(kuò)展。它和C/C++代碼有著良好的交互能力。將Lua引擎嵌入到shttpd中,再使用C編寫一個(gè)(一些)驅(qū)動(dòng)硬件的擴(kuò)展,注冊(cè)成為Lua的函數(shù),形成的系統(tǒng)結(jié)構(gòu)如下圖:這樣的應(yīng)用在嵌入式系統(tǒng)中是有一定代表性的,即,以C實(shí)現(xiàn)底層核心功能,而把系統(tǒng)的易變部分以腳本實(shí)現(xiàn)。大家可以思考在自己的開發(fā)過程中是否可以使用這種技術(shù)。這是LOP的一種具體應(yīng)用模式。(沒有創(chuàng)造一種全新的語言,而是使用Lua)測(cè)試可測(cè)試性是軟件質(zhì)量的一個(gè)度量指標(biāo)好的軟件是設(shè)計(jì)出來的,好的軟件也一定是便于測(cè)試的。一個(gè)難于測(cè)試的軟件的質(zhì)量是難以得到保障的。在今天軟件規(guī)模越來越大的趨勢(shì)下,以下問題是普遍存在的:測(cè)試只能手工進(jìn)行,回歸測(cè)試代價(jià)極大,實(shí)際只能執(zhí)行點(diǎn)測(cè),質(zhì)量無法保證各個(gè)模塊只有集成到一起后才能測(cè)試代碼不經(jīng)過任何單元測(cè)試就集成這些問題的根源都在于缺乏一個(gè)良好的軟件設(shè)計(jì)。一個(gè)好的軟件設(shè)計(jì)應(yīng)該使得單元測(cè)試,模塊測(cè)試和回歸測(cè)試都變得容易,從而保證測(cè)試的廣度和深度,最終產(chǎn)生高質(zhì)量的軟件。除了功能,非功能性需求也必須是可測(cè)試的。所以,可測(cè)試性是軟件設(shè)計(jì)中一個(gè)重要的指標(biāo),是系統(tǒng)架構(gòu)師需要認(rèn)真考慮的問題。測(cè)試驅(qū)動(dòng)的軟件架構(gòu)這里談的是測(cè)試驅(qū)動(dòng)的軟件架構(gòu),而不是測(cè)試驅(qū)動(dòng)的開發(fā)。TDD(TestDrivenDevelopment)是一種開發(fā)方式,是一種編碼實(shí)踐。而測(cè)試驅(qū)動(dòng)的架構(gòu)強(qiáng)調(diào)的是,從提高可測(cè)試性的角度進(jìn)行架構(gòu)設(shè)計(jì)。軟件的測(cè)試分為多個(gè)層次:系統(tǒng)測(cè)試系統(tǒng)測(cè)試是指由測(cè)試人員執(zhí)行的,驗(yàn)證軟件是否完整正確的實(shí)現(xiàn)了需求的測(cè)試。這種測(cè)試中,測(cè)試人員作為用戶的角色,通過程序界面進(jìn)行測(cè)試。在大部分情況下這些工作是手工完成的。在規(guī)范的流程中,這個(gè)過程通常要占到整個(gè)軟件開發(fā)時(shí)間的1/3以上。而當(dāng)有新版本發(fā)布的時(shí)候,盡管只涉及了軟件的一部分,測(cè)試部門依然需要完整的測(cè)試整個(gè)軟件。這是由代碼“副作用”特點(diǎn)決定的。有時(shí)候修改一個(gè)bug可以引發(fā)更多的bug,破壞原來工作正常的代碼。這在測(cè)試中叫回歸測(cè)試(Regressiontest)。對(duì)于規(guī)模較大的軟件,回歸測(cè)試需要很長的時(shí)間,在版本新增功能和錯(cuò)誤修正不多的情況下,回歸測(cè)試可以占到整個(gè)軟件開發(fā)過程了一半以上,嚴(yán)重影響了軟件的交付,也使軟件測(cè)試部門成為軟件開發(fā)流程中的瓶頸。測(cè)試過程自動(dòng)化,是部分解決這個(gè)問題的辦法。作為架構(gòu)師,有必要考慮如何實(shí)現(xiàn)軟件的可自動(dòng)化測(cè)試性。界面自動(dòng)化測(cè)試在沒有圖形化界面以前,字符方式的界面是比較容易進(jìn)行自動(dòng)化測(cè)試的。一個(gè)編寫良好的腳本就可以實(shí)現(xiàn)輸入和對(duì)輸出的檢查。但是對(duì)于圖形化的界面,人的參與似乎變得不可缺少。有一些界面自動(dòng)化的測(cè)試工具,如WinRunner,這些工具可以記錄下測(cè)試人員的操作成為腳本,然后通過回放這些腳本,就可以實(shí)現(xiàn)操作的自動(dòng)化。針對(duì)嵌入式設(shè)備,有TestQuest可以使用,通過在設(shè)備中運(yùn)行一個(gè)類似遠(yuǎn)程桌面的Agent,PC端的測(cè)試工具可以用圖像識(shí)別的方法識(shí)別出不同的組件,并發(fā)送相應(yīng)用戶的輸入。此類工具的基本工作原理如圖:但是這個(gè)過程在實(shí)際中存在三個(gè)問題:可靠性差,經(jīng)常中斷運(yùn)行。要寫一個(gè)可靠的腳本甚至比開發(fā)軟件還要困難。比如,按下一個(gè)按鈕,有時(shí)候一個(gè)對(duì)話框立刻就出現(xiàn),有時(shí)候可能要好幾秒,有時(shí)候甚至不出現(xiàn),操作錄制工具不能自動(dòng)實(shí)現(xiàn)這些判斷,而需要手動(dòng)修改。對(duì)操作結(jié)果的判斷很困難,尤其是非標(biāo)準(zhǔn)的控件。當(dāng)界面修改后,原有代碼很容易失效要應(yīng)用基于圖形界面的自動(dòng)化測(cè)試工具,架構(gòu)師在架構(gòu)的時(shí)候應(yīng)該考慮:界面風(fēng)格如何保持一致。應(yīng)當(dāng)由架構(gòu),而非程序員決定架構(gòu)的風(fēng)格。包括布局,控件大小,相對(duì)位置,文字,對(duì)操作的響應(yīng)方式,超時(shí)時(shí)長,等等。如何在最合適測(cè)試工具的界面和用戶喜歡的界面之中折中。比如,TestQuest是基于圖像識(shí)別的,那么黑白兩色的界面是最有利的,而用戶喜歡的漸進(jìn)色就非常不利。也許讓界面具備自動(dòng)的切換能力最好。對(duì)于已經(jīng)完成的產(chǎn)品,如果架構(gòu)沒有為自動(dòng)化測(cè)試做過考慮,所能應(yīng)用的范圍就非常有限,不過還是有一些思路可以供參考:實(shí)現(xiàn)小規(guī)模的自動(dòng)化腳本。針對(duì)一個(gè)具體的操作流程進(jìn)行測(cè)試,而不是試圖用一個(gè)腳本測(cè)試整個(gè)軟件。一系列的小測(cè)試腳本組成了一個(gè)集合,覆蓋系統(tǒng)的一部分功能。這些測(cè)試腳本可以都以軟件啟動(dòng)時(shí)的狀態(tài)作為基準(zhǔn),所以在狀態(tài)處理上會(huì)比較簡單”猴子測(cè)試”有一定的價(jià)值。所謂猴子測(cè)試,就是隨機(jī)操作鼠標(biāo)和鍵盤。這種測(cè)試完全不理解軟件的功能,可以發(fā)現(xiàn)一些正常測(cè)試無法發(fā)現(xiàn)的錯(cuò)誤。據(jù)微軟內(nèi)部的資料,微軟的一些產(chǎn)品15%的錯(cuò)誤是由“猴子測(cè)試”發(fā)現(xiàn)的。總的來講,基于界面的自動(dòng)化測(cè)試是不成熟的。對(duì)架構(gòu)師而言一定要避免功能只能通過界面才能訪問。要讓界面僅僅是界面,而軟件大部分的功能是獨(dú)立于界面并可以通過其他方式訪問的。上面框架的例子中的設(shè)計(jì)就體現(xiàn)了這一點(diǎn)。思考:如何讓界面具有自我測(cè)試功能?基于消息的自動(dòng)化測(cè)試如果軟件對(duì)外提供基于消息的接口,自動(dòng)化測(cè)試就會(huì)變得簡單的多。上面已經(jīng)提到了固件的TL1接口。對(duì)于界面部分,則應(yīng)該在設(shè)計(jì)的時(shí)候,將純粹的“界面”獨(dú)立出來,讓它盡可能的薄,而其他部分依然可以基于消息提供服務(wù)。在消息的基礎(chǔ)上,用腳本語言包裝成函數(shù)的形式,可以很容易的調(diào)用,并覆蓋消息的各種參數(shù)組合,從而提高測(cè)試的覆蓋率。關(guān)于如何將消息包裝為腳本,可以參考SOAP的實(shí)現(xiàn)。如果使用的不是XML,也可以自己實(shí)現(xiàn)類似的自動(dòng)代碼生成。這些測(cè)試腳本應(yīng)該由開發(fā)人員撰寫,每當(dāng)實(shí)現(xiàn)了一個(gè)新的接口(也就是一條新的消息),就應(yīng)該撰寫相應(yīng)的測(cè)試腳本,并作為項(xiàng)目的一部分保存在代碼庫中。當(dāng)需要執(zhí)行回歸測(cè)試的時(shí)候,只要運(yùn)行一遍測(cè)試腳本即可,大大提高了回歸測(cè)試的效率。所以,為了實(shí)現(xiàn)軟件的自動(dòng)化測(cè)試,提供基于消息的接口是一個(gè)很好的辦法,這讓我們可以在軟件之外獨(dú)立的編寫測(cè)試腳本。在設(shè)計(jì)的時(shí)候可以考慮這個(gè)因素,適當(dāng)?shù)脑黾榆浖⒌闹С帧.?dāng)然,TL1只是一個(gè)例子,根據(jù)項(xiàng)目的需要,可以選擇任何適合的協(xié)議,如SOAP。自動(dòng)化測(cè)試框架在編寫自動(dòng)化測(cè)試腳本的時(shí)候,有很多的工作是重復(fù)的,比如建立socket連接,日志,錯(cuò)誤處理,報(bào)表生成等。同時(shí),對(duì)于測(cè)試人員來說,這些工作可能是比較困難的。因此,設(shè)計(jì)一個(gè)框架,實(shí)現(xiàn)并隱藏這些重復(fù)和復(fù)雜的技術(shù),讓測(cè)試腳本的編寫者將注意力集中在具體的測(cè)試邏輯上。這樣一個(gè)框架應(yīng)該實(shí)現(xiàn)以下功能:完成連接的初始化等基礎(chǔ)工作。捕獲所有的錯(cuò)誤,保證TestCase中的錯(cuò)誤不會(huì)打斷后續(xù)的TestCase執(zhí)行。自動(dòng)檢測(cè)和執(zhí)行TestCase。新增的TestCase是獨(dú)立的腳本文件,無須修改框架的代碼或者配置。消息編解碼,并以函數(shù)的方式提供給TestCase編寫者調(diào)用。方便的工具,如報(bào)表,日志等。自動(dòng)統(tǒng)計(jì)TestCase的運(yùn)行結(jié)果并生成報(bào)告。自動(dòng)化測(cè)試框架的思路和一般的軟件框架是一致的,就是避免重復(fù)勞動(dòng),降低開發(fā)難度。下圖是一個(gè)自動(dòng)化測(cè)試框架的結(jié)構(gòu)圖: 每個(gè)TestCase都必須定義一個(gè)規(guī)定的Run函數(shù),框架將依次調(diào)用,并提供相應(yīng)的庫函數(shù)供TestCase用來發(fā)送命令和獲得結(jié)果。這樣,測(cè)試用例的編寫者就只需要將注意力集中在測(cè)試本身。舉例: defrun(): open_laser() assert(get_laser_state()==ON) insert_error(BIT_ERROR) assert(get_error_bit()==BIT_ERROR)測(cè)試用例的編寫者擁有的知識(shí)是“必須先打開激光器然后才能向線路上插入錯(cuò)誤”。而架構(gòu)師能提供的是消息收發(fā),編解碼,錯(cuò)誤處理,報(bào)表生成等,并將這些為測(cè)試用例編寫者隔離。問題:open_laser,get_laser_state這些函數(shù)是誰寫的?問題:如何進(jìn)一步實(shí)現(xiàn)知識(shí)的解耦?能否有更方便的語言來編寫TestCase?回歸測(cè)試有了自動(dòng)化的測(cè)試腳本和框架,回歸測(cè)試就變得很簡單了。每當(dāng)有新版本發(fā)布時(shí),只需運(yùn)行一遍現(xiàn)有的TestCase,分析測(cè)試報(bào)告,如果有測(cè)試失敗的Case則回歸測(cè)試失敗,需要重新修改,直到所有的Case完全通過。完整的回歸測(cè)試是軟件質(zhì)量的重要保證。集成測(cè)試集成測(cè)試要驗(yàn)證的是系統(tǒng)各個(gè)組成模塊的接口是否工作正常。這是比系統(tǒng)測(cè)試更低層的測(cè)試,通常由開發(fā)人員和測(cè)試人員共同完成。例如在一個(gè)典型的嵌入式系統(tǒng)中,F(xiàn)PGA,固件和界面是常見的三個(gè)模塊。模塊本身還可以劃分為更小的模塊,從而降低復(fù)雜度。嵌入式軟件模塊測(cè)試的常見問題是硬件沒有固件則無法工作,固件沒有界面就無法驅(qū)動(dòng);反過來,界面沒有固件不能完整運(yùn)行,固件沒有硬件甚至無法運(yùn)行。于是沒有經(jīng)過測(cè)試的模塊直到集成的時(shí)候才能完整運(yùn)行,發(fā)現(xiàn)問題后需要考慮所有模塊的問題,定位和解決的代價(jià)都很大。假設(shè)有模塊A和B,各有十個(gè)bug。如果都沒有經(jīng)過模塊測(cè)試直接集成,可以認(rèn)為排錯(cuò)的工作量相當(dāng)于10*10等于100。所以,在設(shè)計(jì)一個(gè)模塊的時(shí)候,首先要考慮,這個(gè)模塊如何單獨(dú)測(cè)試?比如,如果界面和固件之間是通過SOCKET通信的,那么就可以開發(fā)一個(gè)模擬固件,在同樣的端口上提供服務(wù)。這個(gè)模擬固件不執(zhí)行實(shí)際的操作,但是會(huì)響應(yīng)界面的請(qǐng)求并返回模擬的結(jié)果。并且返回的結(jié)果可以覆蓋到各種典型的情況,包括錯(cuò)誤的情況。使用這樣的技術(shù),界面部分幾乎可以得到100%的驗(yàn)證,在集成階段遇到錯(cuò)誤的大大減少。對(duì)固件而言,因?yàn)樘幱谙到y(tǒng)的中間,所以問題復(fù)雜一些。一方面,要讓固件可以通過GUI以外的途徑被調(diào)用;另一方面則要模擬硬件的功能。對(duì)于第一點(diǎn),在設(shè)計(jì)的時(shí)候,要讓接口和實(shí)現(xiàn)分離。接口可以隨意的更換,比如和GUI的接口也許是JSON,同時(shí)還可以提供telnet的TL1接口,但是實(shí)現(xiàn)是完全一樣的。這樣,在和GUI集成之前,就可以通過TL1進(jìn)行完全的測(cè)試固件。對(duì)于第二點(diǎn),則應(yīng)該在設(shè)計(jì)的時(shí)候提取出硬件抽象層,讓固件的主要實(shí)現(xiàn)和寄存器,內(nèi)存地址等因素隔離開來。在沒有硬件或者硬件設(shè)計(jì)未定的時(shí)候?qū)崿F(xiàn)一個(gè)硬件模擬層,來保證固件可以完整運(yùn)行并測(cè)試。單元測(cè)試單元測(cè)試是軟件測(cè)試的最基本單位,是由開發(fā)人員執(zhí)行以保證其所開發(fā)代碼正確的過程。開發(fā)人員應(yīng)該提交經(jīng)過測(cè)試的代碼。未經(jīng)單元測(cè)試的代碼在進(jìn)入軟件后,不僅發(fā)現(xiàn)問題后很難定位,而且通過系統(tǒng)測(cè)試是很難做到對(duì)代碼分支的完全覆蓋的。TDD就是基于這個(gè)層次的開發(fā)模式。單元測(cè)試的粒度一般是函數(shù)或者類,例如下面這個(gè)常用函數(shù):intatoi(constchar*nptr);這是一個(gè)功能非常單一的函數(shù),所以單元測(cè)試對(duì)它非常有效。可以通過單元測(cè)試驗(yàn)證下列情況:一般正常調(diào)用,如”9”,”1000”,”-1”等空的nptr指針非數(shù)字字符串,”abc”,”@#!123”,”123abc”帶小數(shù)點(diǎn)的字符串,“1.1”,”0.111”,”.123”超長字符串超大數(shù)字,”999999999999999999999999999”超過一個(gè)的-號(hào)和位置錯(cuò)誤的-號(hào),”—1”,”-1-“,”-1-2”如果atoi通過了以上測(cè)試,我們就可以放心的將它集成到軟件中去了。由它再引發(fā)問題的概率就很小了(不是完全沒有,因?yàn)槲覀儾荒鼙闅v所有可能,只是挑選有代表性的異常情況進(jìn)行測(cè)試)。以上的例子可以說是單元測(cè)試的典范,但實(shí)際中卻常常不是這么回事。我們常常發(fā)現(xiàn)寫好的函數(shù)很難做單元測(cè)試,不僅工作量很大,效果也不見得好。其根本的原因是,函數(shù)沒有遵循好一些原則:單一功能低耦合反觀atoi的例子,功能單一明確,和其他函數(shù)幾

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論