原文轉(zhuǎn)自:http://blog.csdn.net/apple_guet/article/details/6721186
代碼永遠(yuǎn)會(huì)有BUG,在這方面沒(méi)有最好只有更好。高效是程序員必須作到的事情,無(wú)錯(cuò)是程序員一生的追求。復(fù)用、分而治之、折衷是代碼哲學(xué)的基本思想。模塊化與面向?qū)ο笫菍?shí)現(xiàn)高效無(wú)錯(cuò)代碼的方法。高效無(wú)錯(cuò)代碼需要思想與實(shí)踐的不斷反復(fù)。
1.2.1 命名約定
命令規(guī)范基本上采用了微軟推薦的匈牙利命名法,略有簡(jiǎn)化。
1. 常量
常量由大寫(xiě)字母和數(shù)字組成,中間可以下劃線分隔,如 CPU_8051。
2. 變量
變量由小寫(xiě)(變量類(lèi)型)字母開(kāi)頭,中間以大寫(xiě)字母分隔,可以添加變量域前綴(變量活動(dòng)域前綴以下劃線分隔)。如: v_nAcVolMin(交流電壓最小值)。變量域前綴見(jiàn)下表局部變量,如果變量名的含義十分明顯,則不加前綴,避免煩瑣。如用于循環(huán)的int型變量 i,j,k ;float 型的三維坐標(biāo)(x,y,z)等。
3. 函數(shù)名一般由大寫(xiě)字母開(kāi)頭,中間以大寫(xiě)字母分隔,如SetSystemPara。
函數(shù)命名采用動(dòng)賓形式。如果函數(shù)為最底層,可以考慮用全部小寫(xiě),單詞間采用帶下劃線的形式。如底層圖形函數(shù):pixel、lineto以及讀鍵盤(pán)函數(shù)get_key 等。
4. 符號(hào)名應(yīng)該通用或者有具體含義,可讀性強(qiáng)。尤其是全局變量,靜態(tài)變量含義必須清晰。
C++中的一些關(guān)鍵詞不能作為符號(hào)名使用,如class、new、friend等。符號(hào)名長(zhǎng)度小于31個(gè),與ANSI C 保持一致。命名只能用26個(gè)字母,10個(gè)數(shù)字,以及下劃線‘_’來(lái)組成,不要使用‘$’‘@’等符號(hào)。下劃線‘_’使用應(yīng)該醒目,不能出現(xiàn)在符號(hào)的頭尾,只能出現(xiàn)在符號(hào)中間,且不要連續(xù)出現(xiàn)兩個(gè)。
5. 程序中少出現(xiàn)無(wú)意義的數(shù)字,常量盡量用宏替代。
1.2.2 使用斷言
程序一般分為Debug版本和Release版本,Debug版本用于內(nèi)部調(diào)試,Release版本發(fā)行給用戶使用。斷言assert是僅在Debug版本起作用的宏,它用于檢查“不應(yīng)該”發(fā)生的情況。以下是一個(gè)內(nèi)存復(fù)制程序,在運(yùn)行過(guò)程中,如果assert的參數(shù)為假,那么程序就會(huì)中止(一般地還會(huì)出現(xiàn)提示對(duì)話,說(shuō)明在什么地方引發(fā)了assert)。
//復(fù)制不重疊的內(nèi)存塊
void memcpy(void *pvTo, void *pvFrom, size_t size)
{
void *pbTo = (byte *) pvTo;
void *pbFrom = (byte *) pvFrom;
assert( pvTo != NULL && pvFrom != NULL );
while(size - - > 0 )
*pbTo + + = *pbFrom + + ;
return (pvTo);
}
assert不是一個(gè)倉(cāng)促拼湊起來(lái)的宏,為了不在程序的Debug版本和Release版本引起差別,assert不應(yīng)該產(chǎn)生任何副作用。所以assert不是函數(shù),而是宏。程序員可以把a(bǔ)ssert看成一個(gè)在任何系統(tǒng)狀態(tài)下都可以安全使用的無(wú)害測(cè)試手段。
以下是使用斷言的幾個(gè)原則:
1)使用斷言捕捉不應(yīng)該發(fā)生的非法情況。不要混淆非法情況與錯(cuò)誤情況之間的區(qū)別,后者是必然存在的并且是一定要作出處理的。
2)使用斷言對(duì)函數(shù)的參數(shù)進(jìn)行確認(rèn)。
3)在編寫(xiě)函數(shù)時(shí),要進(jìn)行反復(fù)的考查,并且自問(wèn):“我打算做哪些假定?”一旦確定了的假定,就要使用斷言對(duì)假定進(jìn)行檢查。
4)一般教科書(shū)都鼓勵(lì)程序員們進(jìn)行防錯(cuò)性的程序設(shè)計(jì),但要記住這種編程風(fēng)格會(huì)隱瞞錯(cuò)誤。當(dāng)進(jìn)行防錯(cuò)性編程時(shí),如果“不可能發(fā)生”的事情的確發(fā)生了,則要使用斷言進(jìn)行報(bào)警。
1.2.3 優(yōu)化/效率規(guī)則
規(guī)則一:對(duì)于在中斷函數(shù)/線程和外部函數(shù)中均使用的全局變量應(yīng)用volatile定義。
例如:volatile int ticks;
void timer(void) interrupt 1 //中斷處理函數(shù)
{
ticks++
}
void wait(int interval)
{
tick=0;
while(tick<interval);
}
如果未用volatile,由于while循環(huán)是一個(gè)空循環(huán),編譯器優(yōu)化后(編譯器并不知道此變量在中斷中使用)將會(huì)把循環(huán)優(yōu)化為空操作!這就顯然不對(duì)了。
規(guī)則二:不要編寫(xiě)一條過(guò)分復(fù)雜的語(yǔ)句,緊湊的C++/C代碼并不見(jiàn)到能得到高效率的機(jī)器代碼,卻會(huì)降低程序的可理解性,程序出錯(cuò)誤的幾率也會(huì)提高。
規(guī)則三:變量類(lèi)型編程中應(yīng)用原則:
盡量采用小的類(lèi)型(如果能夠不用“Float”就盡量不要去用)以及無(wú)符號(hào)Unsigned類(lèi)型,因?yàn)榉?hào)運(yùn)算耗費(fèi)時(shí)間較長(zhǎng);
同時(shí)函數(shù)返回值也盡量采用Unsigned類(lèi)型,由此帶來(lái)另外一個(gè)好處:避免不同類(lèi)型數(shù)據(jù)比較運(yùn)算帶來(lái)的隱性錯(cuò)誤。
1.2.4 其他規(guī)則
規(guī)則一:不要編寫(xiě)集多種功能于一身的函數(shù),在函數(shù)的返回值中,不要將正常值和錯(cuò)誤標(biāo)志混在一起。
規(guī)則二:不要將BOOL值TRUE和FALSE對(duì)應(yīng)于1和0進(jìn)行編程。
大多數(shù)編程語(yǔ)言將FALSE定義為0,任何非0值都是TRUE。Visual C++將TRUE定義為1,而Visual Basic則將TRUE定義為-1。
例如:BOOL flag;
…
if(flag) { // do something } // 正確的用法
if(flag==TRUE) { // do something } // 危險(xiǎn)的用法
if(flag==1) { // do something } // 危險(xiǎn)的用法
if(!flag) { // do something } // 正確的用法
if(flag==FALSE) { // do something } // 不合理的用法
if(flag==0) { // do something } // 不合理的用法
規(guī)則三:小心不要將“= =”寫(xiě)成“=”,編譯器不會(huì)自動(dòng)發(fā)現(xiàn)這種錯(cuò)誤。
規(guī)則四:建議統(tǒng)一函數(shù)返回值為無(wú)符號(hào)整形,0代表無(wú)錯(cuò)誤,其他代表錯(cuò)誤類(lèi)型。
1.3 模塊化的C編程
C語(yǔ)言雖然不具備C++的面向?qū)ο蟮某煞?,但仍?yīng)該吸收面向?qū)ο蟮乃枷?,采用模塊化編程思路。
面向?qū)ο蟮乃枷肱c面向?qū)ο蟮恼Z(yǔ)言是兩個(gè)概念。非面向?qū)ο蟮恼Z(yǔ)言依然可以完成面向?qū)ο蟮木幊?,想想C++的誕生吧!C++沒(méi)有理由對(duì)C存在傲慢與偏見(jiàn),不是任何場(chǎng)合C++方法都是解決問(wèn)題的良藥,譬如面對(duì)嵌入式系統(tǒng)效率和空間的雙重需求。注意我們談的是方法,而不是指編譯器。
C在軟件開(kāi)發(fā)上存在的首要問(wèn)題是缺乏對(duì)數(shù)據(jù)存取的控制(封裝),C編程者樂(lè)而不疲的使用著大量extern形式的全局變量在各模塊間交換著數(shù)據(jù)。這樣多個(gè)變量出現(xiàn)在多個(gè)模塊中,剪不斷理還亂,直到有一天終于發(fā)現(xiàn)找一個(gè)“人”好難。一個(gè)東西好吃,智者淺嘗之改進(jìn)之,而愚者只會(huì)直至撐死。應(yīng)在C上多下功夫,還是看看C語(yǔ)言如何實(shí)現(xiàn)模塊化編程方法,在部分程度上具備了OO特性封裝與多態(tài)。
在具體闡述之前,需要明確生存期與可見(jiàn)性的概念。生存期指的是變量在內(nèi)存的生存周期,可見(jiàn)性指的是變量在當(dāng)前位置是否可用。兩者有緊密聯(lián)系,但不能混為一談。一個(gè)人存在但不可見(jiàn)只能解釋成上帝或靈魂,一個(gè)變量存在但不可見(jiàn)卻并非咄咄怪事,模塊化方法正是利用了靜態(tài)函數(shù)、靜態(tài)變量這些“精靈”們特殊的生存期與可見(jiàn)性。
最后需要明確一點(diǎn)的是這里的模塊是以一個(gè).C文件為單位。
規(guī)則一:利用函數(shù)命名規(guī)則和靜態(tài)函數(shù)
模塊中不被其他模塊調(diào)用的內(nèi)部函數(shù)采用以下命名規(guī)則:用全部小寫(xiě),單詞間采用帶下劃線的形式。如底層圖形函數(shù):pixel、lineto以及讀鍵盤(pán)函數(shù)get_key等。這些函數(shù)應(yīng)定義為static靜態(tài)函數(shù),這樣在其他模塊錯(cuò)誤地調(diào)用這些函數(shù)時(shí)編譯器能給出錯(cuò)誤(如BC編譯器)。(注意:有些編譯器不能報(bào)告錯(cuò)誤,但為了代碼風(fēng)格一致和函數(shù)層次清晰,仍建議這樣作)。
規(guī)則二:利用靜態(tài)變量
模塊中不能被其他模塊讀寫(xiě)的全局變量應(yīng)采用static聲明,這樣在其他模塊錯(cuò)誤地讀寫(xiě)這些變量時(shí)編譯器能給出警告(C51編譯器)或錯(cuò)誤(BC編譯器)。
規(guī)則三:引入OO接口概念和指針傳參模塊間的數(shù)據(jù)接口(也就是函數(shù))應(yīng)該事先較充分考慮,需要哪些接口,通過(guò)接口需要操作哪些數(shù)據(jù),盡量作到接口的不變性。
模塊間地?cái)?shù)據(jù)交換盡量通過(guò)接口完成,方法是通過(guò)函數(shù)傳參數(shù),為了保證程序高效和減少堆棧空間,傳大量參數(shù)(如結(jié)構(gòu))應(yīng)采用傳址的方式,通過(guò)指針作為函數(shù)參數(shù)或函數(shù)返回指針,盡量杜絕extern形式的全局變量,請(qǐng)注意是extern形式的全局變量,模塊內(nèi)部的全局變量是允許和必須的。
傳指針參數(shù)增加的開(kāi)銷(xiāo)主要是作參數(shù)的指針和局部指針的數(shù)據(jù)空間(嵌入式系統(tǒng)(如C51)往往由于堆棧空間有限,函數(shù)參數(shù)會(huì)放到外部RAM的堆棧中),增加的代碼開(kāi)銷(xiāo)僅是函數(shù)的調(diào)用,帶來(lái)的是良好的模塊化結(jié)構(gòu),而且使用接口函數(shù)會(huì)比在代碼中多處直接使用全局變量大大節(jié)約代碼空間。對(duì)于需要頻繁訪問(wèn)的變量如果仍采用接口傳遞,函數(shù)調(diào)用的開(kāi)銷(xiāo)是巨大的,這時(shí)應(yīng)考慮仍采用extern全局變量。
以下演示了兩個(gè)C模塊交換數(shù)據(jù):
//Module1.C
OneStruct* void GetOneStruct(void); //獲取模塊1數(shù)據(jù)接口
void SetOneStruct(OneStruct* pOneStruct); //寫(xiě)模塊1數(shù)據(jù)接口
struct OneStruct
{
int m?_imember;
//……
}t1;
//模塊1的數(shù)據(jù)//t1初始化代碼…..
OneStruct* void GetOneStruct(void)
{
OneStruct* pt1; //只需定義一個(gè)局部變量
t1.imember=15;
pt1=&t1;
return pt1;
}
void SetOneStruct(OneStruct* pOneStruct)
{
t1.imember=pOneStruct->imember;
//…….
}
//Module2.C
void OperateOneStruct(void); //模塊2通過(guò)模塊1提供的接口操作模塊1的數(shù)據(jù)
OneStruct* void GetOneStruct(void);
void SetOneStruct(OneStruct* pOneStruct);
void OperateOneStruct(void)
{
OneStruct* pt2; //只需定義一個(gè)局部變量
pt2=GetOneStruct(); //讀取數(shù)據(jù)
SetOneStruct(pt2); //改寫(xiě)數(shù)據(jù)
}
采用接口訪問(wèn)數(shù)據(jù)可以避免一些錯(cuò)誤,因?yàn)楹瘮?shù)返回值只能作右值,全局變量則不然。
例如 cOneChar == 4; 可能被誤為cOneChar = 4;
規(guī)則四:有限的封裝與多態(tài)
不要忘記C++的class源于C的struct,C++的虛函數(shù)機(jī)制實(shí)質(zhì)是函數(shù)指針。為了使數(shù)據(jù)、方法能夠封裝在一起,提高代碼的重用度,如對(duì)于一些與硬件相關(guān)的數(shù)據(jù)結(jié)構(gòu),建議采用在數(shù)據(jù)結(jié)構(gòu)中將訪問(wèn)該數(shù)據(jù)結(jié)構(gòu)的函數(shù)定義為結(jié)構(gòu)內(nèi)部的函數(shù)指針。這樣當(dāng)硬件變化,需要重寫(xiě)訪問(wèn)該硬件的函數(shù),只要將重寫(xiě)的函數(shù)地址賦給該函數(shù)指針,高層代碼由于使用的是函數(shù)指針,所以完全不用動(dòng),實(shí)現(xiàn)代碼重用。而且該函數(shù)指針可以通過(guò)傳參數(shù)或全局變量的方式傳給高層代碼,比較方便。
例如:
struct OneStruct
{
int m?_imember;
int (*func)(int,int);
//……
}t2;
聯(lián)系客服