編程規(guī)范
?
今天人們越來越明白軟件設(shè)計更多地是一種工程而不是一種個人藝術(shù)由于大型產(chǎn)品的開
發(fā)通常由很多的人協(xié)同作戰(zhàn)如果不統(tǒng)一編程規(guī)范最終合到一起的程序其可讀性將較
差這不僅給代碼的理解帶來障礙增加維護階段的工作量同時不規(guī)范的代碼隱含錯誤的
可能性也比較大
BELL實驗室的研究資料表明軟件錯誤中18%左右產(chǎn)生于概要設(shè)計階段15%左右產(chǎn)生于詳細
設(shè)計階段而編碼階段產(chǎn)生的錯誤占的比例則接近50%分析表明編碼階段產(chǎn)生的錯誤當
中語法錯誤大概占20%左右而由于未嚴格檢查軟件邏輯導致的錯誤函數(shù)模塊之間
接口錯誤及由于代碼可理解度低導致優(yōu)化維護階段對代碼的錯誤修改引起的錯誤則占了一半
以上
可見提高軟件質(zhì)量必須降低編碼階段的錯誤率如何有效降低編碼階段的錯誤呢BELL實
驗室的研究人員制定了詳細的軟件編程規(guī)范并培訓每一位程序員最終的結(jié)果把編碼階段
的錯誤降至10%左右同時也降低了程序的測試費用效果相當顯著
本文從代碼的可維護性可讀可理解性可修改性 代碼邏輯與效率函數(shù)模塊接
口可測試性四個方面闡述了軟件編程規(guī)范規(guī)范分成規(guī)則和建議兩種其中規(guī)則部分為強
制執(zhí)行項目而建議部分則不作強制可根據(jù)習慣取舍
2. 編碼規(guī)范
2.1. 排版風格
<規(guī)則 1> 程序塊采用縮進風格編寫縮進為4個空格位排版不混合使用空格和TAB鍵
<規(guī)則2> 在兩個以上的關(guān)鍵字變量常量進行對等操作時它們之間的操作符之前之后
或者前后要加空格進行非對等操作時如果是關(guān)系密切的立即操作符如> 后不應
加空格
采用這種松散方式編寫代碼的目的是使代碼更加清晰例如
(1) 逗號分號只在后面加空格
printf("%d %d %d" , a, b, c);
(2)比較操作符, 賦值操作符"=" "+="算術(shù)操作符"+""%"邏輯操作符"&&""&"位
域操作符"<<""^"等雙目操作符的前后加空格
if(lCurrentTime >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3)"!""~""++""--""&"地址運算符等單目操作符前后不加空格
*pApple = 'a'; // 內(nèi)容操作"*"與內(nèi)容之間
flag = !bIsEmpty; // 非操作"!"與內(nèi)容之間
p = &cMem; // 地址操作"&" 與內(nèi)容之間
i++; // "++","--"與內(nèi)容之間
(4)"->""."前后不加空格
p->id = pId; // "->"指針前后不加空格
由于留空格所產(chǎn)生的清晰性是相對的所以在已經(jīng)非常清晰的語句中沒有必要再留空格
如最內(nèi)層的括號內(nèi)側(cè)(即左括號后面和右括號前面)不要加空格因為在C/C++語言中括號已
經(jīng)是最清晰的標志了
另外在長語句中如果需要加的空格非常多那么應該保持整體清晰而在局部不加空
格
最后即使留空格也不要連續(xù)留兩個以上空格(為了保證縮進和排比留空除外)
<規(guī)則3> 函數(shù)體的開始類的定義結(jié)構(gòu)的定義iffordowhileswitch及case語句
中的程序都應采用縮進方式憑捄蛻}捰稟獨占一行并且位于同一列同時與引用它們的語
句左對齊
例如下例不符合規(guī)范
for ( ... ) {
... // 程序代碼
}
if ( ... )
{
... // 程序代碼
}
void DoExam( void )
{
... // 程序代碼
}
應如下書寫
for ( ... )
{
... // 程序代碼
}
if ( ... )
{
... // 程序代碼
}
void DoExam( void )
{
... // 程序代碼
}
<規(guī)則4> 功能相對獨立的程序塊之間或forifdowhileswitch等語句前后應加一空
行
例如以下例子不符合規(guī)范
例一
if ( ! ValidNi( ni ) )
{
... // 程序代碼
}
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
例二
char *pContext;
int nIndex;
long lCounter;
pContext = new (CString);
if(pContext == NULL)
{
return FALSE;
}
應如下書寫
例一
if ( ! ValidNi( ni ) )
{
... // 程序代碼
}
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
例二
char *pContext;
int nIndex;
long lCounter;
pContext = new (CString);
if(pContext == NULL)
{
return FALSE;
}
<規(guī)則5> ifwhileforcasedefaultdo等語句自占一行
示例如下例子不符合規(guī)范
if(pUserCR == NULL) return;
應如下書寫
if( pUserCR == NULL )
{
return;
}
<規(guī)則6> 若語句較長多于80字符 可分成多行寫劃分出的新行要進行適應的縮進使
排版整齊語句可讀
memset(pData->pData + pData->nCount, 0,
(m_nMax - pData->nCount) * sizeof(LPVOID));
CNoTrackObject* pValue =
(CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot)
;
for ( i = 0, j = 0 ; ( i < BufferKeyword[ WordIndex ].nWordLength )
&& ( j < NewKeyword.nWordLength ) ; i ++ , j ++ )
{
... // 程序代碼
}
<規(guī)則7> 一行最多寫一條語句
示例如下例子不符合規(guī)范
rect.length = 0 ; rect.width = 0 ;
rect.length = width = 0;
都應書寫成
rect.length = 0 ;
rect.width = 0 ;
<規(guī)則8> 對結(jié)構(gòu)成員賦值等號對齊
示例
rect.top = 0;
rect.left = 0;
rect.right = 300;
rect.bottom = 200;
<規(guī)則9> #define的各個字段對齊
以下示例不符合規(guī)范
#define MAX_TASK_NUMBER 100
#define LEFT_X 10
#define BOTTOM_Y 400
應書寫成
#define MAX_TASK_NUMBER 100
#define LEFT_X 10
#define BOTTOM_Y 400
<規(guī)則10> 不同類型的操作符混合使用時使用括號給出優(yōu)先級
如本來是正確的代碼
if( year % 4 == 0 || year % 100 != 0 && year % 400 == 0 )
如果加上括號則更清晰
if((year % 4) == 0 || ((year % 100) != 0 && (year % 400) == 0))
2.2. 可理解性
2.2.1.注釋
注釋的原則是有助于對程序的閱讀理解注釋不宜太多也不能太少太少不利于代碼理解
太多則會對閱讀產(chǎn)生干擾因此只在必要的地方才加注釋而且注釋要準確易懂盡可能
簡潔注釋量一般控制在30%到50%之間
<規(guī)則1> 程序在必要的地方必須有注釋注釋要準確易懂簡潔
例如如下注釋意義不大
/* 如果bReceiveFlag 為 TRUE */
if ( bReceiveFlag == TRUE)
而如下的注釋則給出了額外有用的信息
/* 如果mtp 從連接處獲得一個消息*/
if ( bReceiveFlag == TURE)
<規(guī)則2> 注釋應與其描述的代碼相近對代碼的注釋應放在其上方或右方對單條語句的注
釋相鄰位置不可放在下面如放于上方則需與其上面的代碼用空行隔開
示例如下例子不符合規(guī)范
例子1
/* 獲得系統(tǒng)指針和網(wǎng)絡(luò)指針的副本 */
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
例子2
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
/*獲得系統(tǒng)指針和網(wǎng)絡(luò)指針的副本 */
應如下書寫
/*獲得系統(tǒng)指針和網(wǎng)絡(luò)指針的副本 */
nRepssnInd = SsnData[ index ].nRepssnIndex ;
nRepssnNi = SsnData[ index ].ni ;
<規(guī)則3> 對于所有的常量變量數(shù)據(jù)結(jié)構(gòu)聲明(包括數(shù)組結(jié)構(gòu)類枚舉等)如果其命
名不是充分自注釋的在聲明時都必須加以注釋說明其含義
示例
/* 活動任務的數(shù)量 */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /*活動任務的數(shù)量 */
/* 帶原始用戶信息的SCCP接口 */
enum SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND , /* 向SCCP用戶報告單元數(shù)據(jù)已經(jīng)到達 */
N_UNITDATA_REQ , /* SCCP用戶的單元數(shù)據(jù)發(fā)送請求 */
}
<規(guī)則4> 頭文件源文件的頭部應進行注釋注釋必須列出文件名作者目的功
能修改日志等
例如
/*********************************************
文件名
編寫者
編寫日期
簡要描述
修改記錄
********************************************/
說明摷蛞 枋鰯一項描述本文件的目的和功能等撔薷募鍬紨是修改日志列表每條修改
記錄應包括修改日期修改者及修改內(nèi)容簡述
<規(guī)則5> 函數(shù)頭部應進行注釋列出函數(shù)的目的功能輸入參數(shù)輸出參數(shù)修改日志
等
形式如下
/*************************************************
函數(shù)名稱
簡要描述 // 函數(shù)目的功能等的描述
輸入 // 輸入?yún)?shù)說明包括每個參數(shù)的作用取值說明及參數(shù)間關(guān)
系
輸出 // 輸出參數(shù)的說明返回值的說明
修改日志
*************************************************/
對一些復雜的函數(shù)在注釋中最好提供典型用法
<規(guī)則6> 仔細定義并明確公共變量的含義作用取值范圍及使用方法
在對變量聲明的同時應對其含義作用取值范圍及使用方法進行注釋說明同時若有必
要還應說明與其它變量的關(guān)系明確公共變量與操作此公共變量的函數(shù)或過程的關(guān)系如訪
問修改及創(chuàng)建等
示例
/* SCCP轉(zhuǎn)換時錯誤代碼 */
/* 全局錯誤代碼含義如下 */ // 變量作用含義
/* 0 成功 1 GT 表錯誤 2 GT 錯誤 其它值未使用 */ // 變量
取值范圍
<規(guī)則7> 對指針進行充分的注釋說明對其作用含義使用范圍注意事項等說明清楚
在對指針變量特別是比較復雜的指針變量聲明時應對其含義作用及使用范圍進行注釋
說明如有必要還應說明其使用方法注意事項等
示例
/* 學生記錄列表的頭指針 */
/* 當在此模塊中創(chuàng)建該列表時該頭指針必須初始化 */
/* 這樣可以利用GetListHead()獲得這一列表*/ //指針作用含義
/* 該指針只在本模塊使用其它模塊通過調(diào)用GetListHead()獲取*/
/* 當使用時必須保證它非空 */ //使用范圍方法
STUDENT_RECORD *pStudentRecHead;
<規(guī)則8> 對重要代碼段的功能意圖進行注釋提供有用的額外的信息并在該代碼段的
結(jié)束處加一行注釋表示該段代碼結(jié)束
示例
/* 可選通道的組合 */
if ((gsmBCIe31->radioChReq >= DUAL_HR_RCR)
&& (gsmBCIe32->radioChReq >= DUAL_HR_RCR))
{
gsmBCIe31->radioChReq = FR_RCR;
gsmBCIe32->radioChReq = FR_RCR;
}
else if ((gsmBCIe31->radioChReq >= DUAL_HR_RCR)
&& (gsmBCIe32->radioChReq == FR_RCR) )
{
gsmBCIe31->radioChReq = FR_RCR;
}
else if ((gsmBCIe31->radioChReq == FR_RCR)
&& (gsmBCIe32->radioChReq >= DUAL_HR_RCR))
{
gsmBCIe32->radioChReq = FR_RCR;
}
/* 本塊結(jié)束 ( 可選通道組合 ) */
<規(guī)則9> 在switch語句中對沒有break語句的case分支加上注釋說明
示例
switch(SubT30State)
{
case TA0:
AT(CHANNEL, "AT+FCLASS=1\r", 0);
if(T30Status != 0)
{
return(1);
}
InitFax(); /* 準備發(fā)送傳真 */
AT(CHANNEL, "ATD\r",-1); /*發(fā)送CNG 接收 CED 和 HDLC 標志*/
T1_Flg = 1;
iResCode = 0;
/* 沒有 break; */
case TA1:
iResCode = GetModemMsg(CHANNEL);
break;
default:
break;
}
<規(guī)則 10> 維護代碼時要更新相應的注釋刪除不再有用的注釋
保持代碼注釋的一致性避免產(chǎn)生誤解
2.2 命名
本文列出Visual C++的標識符命名規(guī)范
<規(guī)則 1> 標識符縮寫
形成縮寫的幾種技術(shù)
1) 去掉所有的不在詞頭的元音字母如screen寫成scrn, primtive寫成prmv
2) 使用每個單詞的頭一個或幾個字母如Channel Activation寫成ChanActiv
Release Indication寫成RelInd
3) 使用變量名中每個有典型意義的單詞如Count of Failure寫成FailCnt
4) 去掉無用的單詞后綴 ing, ed等如Paging Request寫成PagReq
5) 使用標準的或慣用的縮寫形式包括協(xié)議文件中出現(xiàn)的縮寫形式如BSIC(Base
Station Identification Code)MAP(Mobile Application Part)
關(guān)于縮寫的準則
1) 縮寫應該保持一致性如Channel不要有時縮寫成Chan有時縮寫成ChLength有時
縮寫成Len有時縮寫成len
2) 在源代碼頭部加入注解來說明協(xié)議相關(guān)的非通用縮寫
3) 標識符的長度不超過32個字符
<規(guī)則2> 變量命名約定
參照匈牙利記法即
[作用范圍域前綴] + [前綴] + 基本類型 + 變量名
其中
前綴是可選項以小寫字母表示
基本類型是必選項以小寫字母表示
變量名是必選項可多個單詞(或縮寫)合在一起每個單詞首字母大寫
前綴列表如下
前綴 意義 舉例
g_ Global 全局變量 g_MyVar
m_ 類成員變量或模塊級變量 m_ListBox, m_Size
s_ static 靜態(tài)變量 s_Count
h Handle 句柄 hWnd
p Pointer 指針 pTheWord
lp Long Point 長指針 lpCmd
a Array 數(shù)組 aErr
基本類型列表如下
基本類型 意義 舉例
b Boolean 布爾 bIsOK
by Byte 字節(jié) byNum
c Char 字符 cMyChar
i或n Intger 整數(shù) nTestNumber
u Unsigned integer 無符號整數(shù) uCount
ul Unsigned Long 無符號長整數(shù) ulTime
w Word 字 wPara
dw Double Word 雙字 dwPara
l Long 長型 lPara
f Float 浮點數(shù) fTotal
s String 字符串 sTemp
sz NULL結(jié)束的字符串 szTrees
fn Funtion 函數(shù) fnAdd
enm 枚舉型 enmDays
x,y x,y坐標
<規(guī)則3> 宏和常量的命名
宏和常量的命名規(guī)則單詞的字母全部大寫各單詞之間用下劃線隔開命名舉例
#define MAX_SLOT_NUM 8
#define EI_ENCR_INFO 0x07
const int MAX_ARRAY
<規(guī)則4> 結(jié)構(gòu)和結(jié)構(gòu)成員的命名
結(jié)構(gòu)名各單詞的字母均為大寫單詞間用下劃線連接可用或不用typedef但是要保持一
致不能有的結(jié)構(gòu)用typedef有的又不用如
typedef struct LOCAL_SPC_TABLE_STRU
{
char cValid;
int nSpcCode[MAX_NET_NUM];
} LOCAL_SPC_TABLE ;
結(jié)構(gòu)成員的命名同變量的命名規(guī)則
<規(guī)則5> 枚舉和枚舉成員的命名
枚舉名各單詞的字母均為大寫單詞間用下劃線隔開
枚舉成員的命名規(guī)則單詞的字母全部大寫各單詞之間用下劃線隔開要求各成員的第一
個單詞相同命名舉例
typdef enum
{
LAPD_ MDL_ASSIGN_REQ,
LAPD_MDL_ASSIGN_IND,
LAPD_DL_DATA_REQ,
LAPD_DL_DATA_IND,
LAPD_DL_UNIT_DATA_REQ,
LAPD_DL_UNIT_DATA_IND,
} LAPD_PRMV_TYPE;
<規(guī)則6> 類的命名
前綴 意義 舉例
C 類 CMyClass
CO COM類 COMMyObjectClass
CF COM class factory CFMyClassFactory
I COM interface class IMyInterface
CImpl COM implementation class CImplMyInterface
<規(guī)則7> 函數(shù)的命名
單詞首字母為大寫其余均為小寫單詞之間不用下劃線函數(shù)名應以一個動詞開頭即函
數(shù)名應類似摱 黿峁箶命名舉例
void PerformSelfTest(void) ;
void ProcChanAct(MSG_CHAN_ACTIV *pMsg, UC MsgLen);
2.3. 可維護性
<規(guī)則1> 在邏輯表達式中使用明確的邏輯判斷
示例如下邏輯表達式不規(guī)范
1) if ( strlen(strName) )
2) for ( index = MAX_SSN_NUMBER; index ; index -- )
3) while ( p && *p ) // 假設(shè)p為字符指針
應改為如下
1) if ( strlen(strName) != 0 )
2) for ( index = MAX_SSN_NUMBER; index != 0 ; index -- )
3) while ((p != NULL) && (*p != '\0' ))
<規(guī)則2> 預編譯條件不應分離一完整的語句
不正確
if (( cond == GLRUN)
#ifdef DEBUG
|| (cond == GLWAIT)
#endif
)
{
}
正確
#ifdef DEBUG
if( cond == GLRUN || cond == GLWAIT )
#else
if( cond == GLRUN )
#endif
{
}
<規(guī)則3> 在宏定義中合并預編譯條件
不正確
#ifdef EXPORT
for ( i = 0; i < MAX_MSXRSM; i++ )
#else
for ( i = 0; i < MAX_MSRSM; i++ )
#endif
正確
頭文件中
#ifdef EXPORT
#define MAX_MS_RSM MAX_MSXRSM
#else
#define MAX_MS_RSM MAX_MSRSM
#endif
源文件中
for( i = 0; i < MAX_MS_RSM; i++ )
<規(guī)則4> 使用宏定義表達式時要使用完備的括號
如下的宏定義表達式都存在一定的隱患
#define REC_AREA(a, b) a * b
#define REC_AREA(a, b) (a * b)
#define REC_AREA(a, b) (a) * (b)
正確的定義為
#define REC_AREA(a, b) ((a) * (b))
<規(guī)則5> 宏所定義的多條表達式應放在大括號內(nèi)
示例下面的語句只有宏中的第一條表達式被執(zhí)行為了說明問題for語句的書寫稍不符
規(guī)范
#define INIT_RECT_VALUE( a, b ) a = 0 ; b = 0 ;
for ( index = 0 ; index < RECT_TOTAL_NUM ; index ++ )
INIT_RECT_VALUE( rect.a, rect.b ) ;
正確的用法應為
#define INIT_RECT_VALUE( a, b ) { a =
0 ; b = 0 ; }
for ( index = 0 ; index < RECT_TOTAL_NUM ; index ++ )
{
INIT_RECT_VALUE( rect[ index ].a, rect[ index ].b ) ;
}
<規(guī)則6> 宏定義不能隱藏重要的細節(jié)避免有returnbreak等導致程序流程轉(zhuǎn)向的語句
如下例子是不規(guī)范的應用其中隱藏了程序的執(zhí)行流程
#define FOR_ALL for(i = 0; i < SIZE; i++)
/* 數(shù)組 c 置0 */
FOR_ALL
{
c = 0;
}
#define CLOSE_FILE { fclose
(fp_local); fclose
(fp_urban);
return; }
<規(guī)則7> 使用宏時不允許參數(shù)發(fā)生變化
下面的例子隱藏了重要的細節(jié)隱含了錯誤
#define SQUARE ((x) * (x))
.
.
w = SQUARE(++value);
這個引用將被展開稱
w = ((++value) * (++value));
其中value累加了兩次與設(shè)計思想不符正確的用法是
w = SQUARE(x);
x++;
<規(guī)則8> 當ifwhilefor等語句的程序塊為摽諗時使用搟}敺擰_
while ( *s++ == *t++ ) ;
以上代碼不符合規(guī)范正確的書寫方式為
while( *s++ == *t++ )
{
/* 無循環(huán)體 */
}
或
while( *s++ == *t++ )
{
}
<規(guī)則9> 結(jié)構(gòu)中元素布局合理一行只定義一個元素
如下例子不符合規(guī)范
typedef struct
{
_UI left, top, right, bottom;
} RECT;
應書寫稱
typedef struct
{
_UI left; /* 矩形左側(cè) x 坐標 */
_UI top;
_UI right;
_UI bottom;
} RECT;
<規(guī)則10> 枚舉值從小到大順序定義
<規(guī)則11> 包含頭文件時使用撓嘍月肪稊不使用摼 月肪稊
-------------------------------------------------------------第 16 頁
如下引用
#include "c:\switch\inc\def.inc"
應改為
#include "inc\def.inc"
或
#include "def.inc"
<規(guī)則12> 不允許使用復雜的操作符組合等
下面用法不好
iMaxVal = ( (a > b ? a : b) > c ? (a > b ? a : b) : c );
應該為
iTemp = ( a > b ? a : b);
iMaxVal = (iTemp > b ? iTemp : b);
不要把"++""--"操作符與其他如"+=""-="等組合在一起形成復雜奇怪的表達式如下的
表達式那以理解
*pStatPoi++ += 1;
*++pStatPoi += 1;
應分別改為
*pStatPoi += 1;
pStatPoi++;
和
++pStatPoi;
*pStatPoi += 1;
<規(guī)則13> 函數(shù)和過程中關(guān)系較為緊密的代碼盡可能相鄰
如初始化代碼應放在一起不應在中間插入實現(xiàn)其它功能的代碼以下代碼不符合規(guī)范,
for (uiUserNo = 0; uiUserNo < MAX_USER_NO; uiUserNo++)
{
...; /* 初始化用戶數(shù)據(jù) */
}
pSamplePointer = NULL;
g_uiCurrentUser = 0; /* 設(shè)置當前用戶索引號 */
-------------------------------------------------------------第 17 頁
應必為
for (uiUserNo = 0; uiUserNo < MAX_USER_NO; uiUserNo++)
{
...; /* 初始化用戶數(shù)據(jù) */
}
g_uiCurrentUser = 0; /* 設(shè)置當前用戶索引號 */
pSamplePointer = NULL;
<規(guī)則14> 每個函數(shù)的源程序行數(shù)原則上應該少于200行
對于消息分流處理函數(shù)完成的功能統(tǒng)一但由于消息的種類多可能超過200行的限制
不屬于違反規(guī)定
<規(guī)則15> 語句嵌套層次不得超過5層
嵌套層次太多增加了代碼的復雜度及測試的難度容易出錯增加代碼維護的難度
<規(guī)則16> 用sizeof來確定結(jié)構(gòu)聯(lián)合或變量占用的空間
這樣可提高程序的可讀性可維護性同時也增加了程序的可移植性
<規(guī)則17> 避免相同的代碼段在多個地方出現(xiàn)
當某段代碼需在不同的地方重復使用時應根據(jù)代碼段的規(guī)模大小使用函數(shù)調(diào)用或宏調(diào)用的
方式代替這樣對該代碼段的修改就可在一處完成增強代碼的可維護性
<規(guī)則18> 使用強制類型轉(zhuǎn)換
示例
USER_RECORD *pUser;
pUser = (USER_RECORD *) malloc (MAX_USER * sizeof(USER_RECORD));
<規(guī)則19> 避免使用 goto 語句
<規(guī)則20> 避免產(chǎn)生摮絳蚪釘program knots在循環(huán)語句中盡量避免breakgoto的
使用
如下例子
for( i = 0; i < n; i++)
{
bEof = fscanf( pInputFile, "%d;", &x);
if( bEof == EOF )
-------------------------------------------------------------第 18 頁
{
break;
}
nSum += x;
}
最好按以下方式書寫避免程序打摻釘
for( i = 0; i < n && bEof= EOF; i++)
{
bEof = fscanf( pInputFile, "%d;", &x);
if( bEof!= EOF )
{
nSum += x;
}
}
<規(guī)則21> 功能相近的一組常量最好使用枚舉來定義
不推薦定義方式
/* 功能寄存器值 */
#define ERR_DATE 1 /* 日期錯誤 */
#define ERR_TIME 2 /* 時間錯誤 */
#define ERR_TASK_NO 3 /* 任務號錯誤 */
推薦按如下方式書寫
/*功能寄存器值 */
enum ERR_TYPE
{
ERR_DATE = 1, /*日期錯誤 */
ERR_TIME = 2, /*時間錯誤 */
ERR_TASK_NO = 3 /* 任務號錯誤 */
}
<規(guī)則22> 每個函數(shù)完成單一的功能不設(shè)計多用途面面俱到的函數(shù)
多功能集于一身的函數(shù)很可能使函數(shù)的理解測試維護等變得困難
使函數(shù)功能明確化增加程序可讀性亦可方便維護測試
<建議1> 循環(huán)判斷語句的程序塊部分用花括號括起來即使只有一條語句
如
-------------------------------------------------------------第 19 頁
if( bCondition == TRUE )
bFlag = YES;
建議按以下方式書寫
if( bCondition == TRUE )
{
bFlag = YES;
}
這樣做的好處是便于代碼的修改增刪
<建議2> 一行只聲明一個變量
不推薦的書寫方式
void DoSomething(void)
{
int Amicrtmrs, nRC;
int nCode, nStatus;
推薦做法
void DoSomething(void)
{
int nAmicrtmrs; /* ICR 計時器 */
int nRC; /* 返回碼 */
int nCode; /* 訪問碼 */
int nStatus; /* 處理機狀態(tài) */
<建議3> 使用專門的初始化函數(shù)對所有的公共變量進行初始化
<建議4> 使用可移植的數(shù)據(jù)類型盡量不要使用與具體硬件或軟件環(huán)境關(guān)系密切的變量
<建議5> 用明確的函數(shù)實現(xiàn)不明確的語句功能
示例如下語句的功能不很明顯
value = ( a > b ) ? a : b ;
改為如下就很清晰了
int max( int a, int b )
{
return ( ( a > b ) ? a : b ) ;
-------------------------------------------------------------第 20 頁
}
value = max( a, b ) ;
或改為如下
#define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) )
value = MAX( a, b ) ;
4. 程序正確性效率
<規(guī)則1> 嚴禁使用未經(jīng)初始化的變量
引用未經(jīng)初始化的變量可能會產(chǎn)生不可預知的后果特別是引用未經(jīng)初始化的指針經(jīng)常會導
致系統(tǒng)崩潰需特別注意聲明變量的同時初始化除了能防止引用未經(jīng)初始化的變量外
還可能生成更高效的機器代碼
<規(guī)則2> 定義公共指針的同時對其初始化
這樣便于指針的合法性檢查防止應用未經(jīng)初始化的指針建議對局部指針也在定義的同時
初始化形成習慣
<規(guī)則3> 較大的局部變量2K以上應聲明成靜態(tài)類型static 避免占用太多的堆棧空
間
避免發(fā)生堆棧溢出出現(xiàn)不可預知的軟件故障
<規(guī)則4> 防止內(nèi)存操作越界
說明內(nèi)存操作主要是指對數(shù)組指針內(nèi)存地址等的操作內(nèi)存操作越界是軟件系統(tǒng)主要
錯誤之一后果往往非常嚴重所以當我們進行這些操作時一定要仔細小心
A 數(shù)組越界
char aMyArray[10];
for( i = 0; i <= 10; i++ )
{
aMyArray = 0; //當i等于10時將發(fā)生越界
}
B 指針操作越界
char aMyArray[10];
char *pMyArray;
-------------------------------------------------------------第 21 頁
pMyArray = aMyArray;
--pMyArray; // 越界
pMyArray = aMyArray;
pMyArray += 10; // 越界
<規(guī)則5> 減少沒必要的指針使用特別是較復雜的指針如指針的指針數(shù)組的指針指針
的數(shù)組函數(shù)的指針等
用指針雖然靈活但也對程序的穩(wěn)定性造成一定威脅主要原因是當要操作一個指針時此
指針可能正指向一個非法的地址安安全全地使用一個指針并不是一件容易的事情
<規(guī)則6> 防止引用已經(jīng)釋放的內(nèi)存空間
在實際編程過程中稍不留心就會出現(xiàn)在一個模塊中釋放了某個內(nèi)存塊如指針 而另一
模塊在隨后的某個時刻又使用了它要防止這種情況發(fā)生
<規(guī)則7> 程序中分配的內(nèi)存申請的文件句柄在不用時應及時釋放或關(guān)閉
分配的內(nèi)存不釋放以及文件句柄不關(guān)閉是較常見的錯誤而且稍不注意就有可能發(fā)生這
類錯誤往往會引起很嚴重后果且難以定位
<規(guī)則8> 注意變量的有效取值范圍防止表達式出現(xiàn)上溢或下溢
示例
unsigned char cIndex = 10;
while( cIndex-- >= 0 )
{
} //將出現(xiàn)下溢
當cIndex等于0 時再減1不會小于0 而是0xFF故程序是一個死循環(huán)
char chr = 127;
chr += 1; //127為chr的邊界值再加1將使chr上溢到-128而不是128
<規(guī)則9> 防止精度損失
以下代碼將產(chǎn)生精度丟失
#define DELAY_MILLISECONDS 10000
char time;
time = DELAY_MILLISECONDS;
WaitTime( time );
代碼的本意是想產(chǎn)生10秒鐘的延時然而由于time為字符型變量只取DELAY_MILLISECONDS
的低字節(jié)高位字節(jié)將丟失結(jié)果只產(chǎn)生了16毫秒的延時
-------------------------------------------------------------第 22 頁
<規(guī)則10> 防止操易混淆的作符拼寫錯誤
形式相近的操作符最容易引起誤用如C/C++中的=斢霌==敗|斢霌||敗&斢霌&&數(shù)齲
羝蔥創(chuàng)砹耍 嘁肫韃灰歡芄患觳槌隼礎(chǔ)_
示例如把&斝闖蓳&&敚蚍 粗_
bRetFlag = ( pMsg -> bRetFlag & RETURN_MASK ) ;
被寫為
bRetFlag = ( pMsg -> bRetFlag && RETURN_MASK ) ;
<規(guī)則11> 使用無符號類型定義位域變量
示例
typedef struct
{
int bit1 : 1;
int bit2 : 1;
int bit3 : 1;
} bit;
bit.bit1 = 1;
bit.bit2 = 3;
bit.bit3 = 6;
printf("%d, %d, %d", bit.bit1, bit.bit2, bit.bit3 );
輸出結(jié)果為-1,-1, -2不是: 1,3,6.
<規(guī)則12> switch語句的程序塊中必須有default語句
對不期望的情況包括異常情況進行處理保證程序邏輯嚴謹
<規(guī)則13> 當聲明用于分布式環(huán)境或不同CPU間通信環(huán)境的數(shù)據(jù)結(jié)構(gòu)時必須考慮機器的字節(jié)
順序使用的位域也要有充分的考慮
比如Intel CPU與68360 CPU在處理位域及整數(shù)時其在內(nèi)存存放的撍承驍正好相反
示例假如有如下短整數(shù)及結(jié)構(gòu)
unsigned short int exam ;
typedef struct _EXAM_BIT_STRU
{ /* Intel 68360 */
unsigned int A1 : 1 ; /* bit 0 2 */
unsigned int A2 : 1 ; /* bit 1 1 */
unsigned int A3 : 1 ; /* bit 2 0 */
} _EXAM_BIT ;
-------------------------------------------------------------第 23 頁
如下是Intel CPU生成短整數(shù)及位域的方式
內(nèi)存 0 1 2 ... 從低到高以字節(jié)為單位
exam exam低字節(jié) exam高字節(jié)
內(nèi)存 0 bit 1 bit 2 bit ... 字節(jié)的各撐粩
_EXAM_BIT A1 A2 A3
如下是68360 CPU生成短整數(shù)及位域的方式
內(nèi)存 0 1 2 ... 從低到高以字節(jié)為單位
exam exam高字節(jié) exam低字節(jié)
內(nèi)存 0 bit 1 bit 2 bit ... 字節(jié)的各撐粩
_EXAM_BIT A3 A2 A1
<規(guī)則14> 編寫可重入函數(shù)時應注意局部變量的使用如編寫C/C++語言的可重入函數(shù)時
應使用auto即缺省態(tài)局部變量或寄存器變量
可重入性是指函數(shù)可以被多個任務進程調(diào)用在多任務操作系統(tǒng)中函數(shù)是否具有可重入性
是非常重要的因為這是多個進程可以共用此函數(shù)的必要條件另外編譯器是否提供可重
入函數(shù)庫與它所服務的操作系統(tǒng)有關(guān)只有操作系統(tǒng)是多任務時編譯器才有可能提供可
重入函數(shù)庫如DOS下BC和MSC等就不具備可重入函數(shù)庫因為DOS是單用戶單任務操作系
統(tǒng)
編寫C/C++語言的可重入函數(shù)時不應使用static局部變量否則必須經(jīng)過特殊處理才能
使函數(shù)具有可重入性
<規(guī)則15> 編寫可重入函數(shù)時若使用全局變量則應通過關(guān)中斷信號量即P V操作
等手段對其加以保護
<規(guī)則16> 結(jié)構(gòu)中的位域應盡可能相鄰結(jié)構(gòu)中的位域在開始處應對齊撟紙跀或撟謹?shù)倪?
界
這樣可減少結(jié)構(gòu)占用的內(nèi)存空間減少CPU處理位域的時間提高程序效率
示例如下結(jié)構(gòu)中的位域布局不合理假設(shè)例子在Intel CPU環(huán)境下
typedef struct _EXAMPLE_STRU
{
unsigned int nExamOne : 6 ;
unsigned int nExamTwo : 3 ; // 此位域跨越字節(jié)摻喚訑處
unsigned int nExamThree : 4 ;
} _EXAMPLE ;
應改為如下按字節(jié)對齊
typedef struct _EXAMPLE_STRU
{
unsigned int nExamOne : 6 ;
unsigned int nFreeOne : 2 ; // 保留bit位使下個位域從字節(jié)開始
unsigned int nExamTwo : 3 ; // 此位域從新的字節(jié)處開始
unsigned int nExamThree : 4 ;
} _EXAMPLE ;
<規(guī)則17> 避免函數(shù)中不必要語句防止程序中的垃圾代碼預留代碼應以注釋的方式出
現(xiàn)
程序中的垃圾代碼不僅占用額外的空間而且還常常影響程序的功能與性能很可能給程序
的測試維護等造成不必要的麻煩
<規(guī)則18> 通過對系統(tǒng)數(shù)據(jù)結(jié)構(gòu)的劃分與組織的改進以及對程序算法的優(yōu)化來提高空間效
率
這種方式是解決軟件空間效率的根本辦法
示例如下記錄學生學習成績的結(jié)構(gòu)不合理
typedef unsigned char _UC ;
typedef unsigned int _UI ;
typedef struct _STUDENT_SCORE_STRU
{
_UC szName[ 8 ] ;
_UC cAge ;
_UC cSex ;
_UC cClass ;
_UC cSubject ;
float fScore ;
} _STUDENT_SCORE ;
因為每位學生都有多科學習成績故如上結(jié)構(gòu)將占用較大空間應如下改進分為兩個結(jié)
構(gòu) 總的存貯空間將變小操作也變得更方便
typedef struct _STUDENT_STRU
{
_UC szName[ 8 ] ;
_UC cAge ;
_UC cSex ;
_UC cClass ;
} _STUDENT ;
typedef struct _STUDENT_SCORE_STRU
{
_UI iStudentIndex ;
_UC cSubject ;
float fScore ;
} _STUDENT_SCORE ;
<規(guī)則19> 循環(huán)體內(nèi)工作量最小化
應仔細考慮循環(huán)體內(nèi)的語句是否可以放在循環(huán)體之外使循環(huán)體內(nèi)工作量最小從而提高程
序的時間效率
示例如下代碼效率不高
for ( i= 0 ; i< MAX_ADD_NUMBER ; i++ )
{
nSum += i;
nBackSum = nSum ; /* 備份和 */
}
語句搉BackSum = nSum ;斖耆 梢苑旁趂or語句之后如下
for ( i = 0 ; i < MAX_ADD_NUMBER ; i ++ )
{
nSum += i ;
}
nBackSum = nSum ; /*備份和 */
<規(guī)則20> 在多重循環(huán)中應將最忙的循環(huán)放在最內(nèi)層
<規(guī)則21> 避免循環(huán)體內(nèi)含判斷語句將與循環(huán)變量無關(guān)的判斷語句移到循環(huán)體外
目的是減少判斷次數(shù)循環(huán)體中的判斷語句是否可以移到循環(huán)體外要視程序的具體情況而
言一般情況與循環(huán)變量無關(guān)的判斷語句可以移到循環(huán)體外而有關(guān)的則不可以
<規(guī)則22> 盡量用乘法或其它方法代替除法特別是浮點運算中的除法在時間效率要求不
是特別嚴格時要優(yōu)先保證程序的可讀性
說明浮點運算除法要占用較多CPU資源
示例如下表達式運算可能要占較多CPU資源
#define PAI 3.1416
fRadius = fCircleLength / ( 2 * PAI ) ;
應如下把浮點除法改為浮點乘法
#define PAI_RECIPROCAL ( 1 / 3.1416 ) // 編譯器編譯時將生成具體浮點數(shù)
fRadius = fCircleLength * PAI_RECIPROCAL / 2 ;
<規(guī)則23> 用++敚--敳僮鞔 鎿+=1敚-=1敚 岣叱絳蛩俁取_
<規(guī)則24> 系統(tǒng)輸入如用戶輸入 系統(tǒng)輸出如信息包輸出系統(tǒng)資源操作如內(nèi)存
分配文件及目錄操作網(wǎng)絡(luò)操作如通信調(diào)用等 任務之間的操作如通信調(diào)用
等時必須進行錯誤超時或者異常處理
<建議 1> 定義字符串變量的同時將其初始化為空即摂以避免無限長字符串
<建議 2> 在switch語句中將經(jīng)常性的處理放在前面
2.5. 接口
<規(guī)則1> 頭文件應采用 #ifndef / #define / #endif 的方式來防止多次被嵌入
示例如下
假設(shè)頭文件為揇EF.INC"則其內(nèi)容應為
#ifndef __DEF_INC
#define __DEF_INC
...
#endif
<規(guī)則2> 去掉沒有必要的公共變量編程時應盡量少用公共變量
公共變量是增大模塊間耦合的原因之一故應減少沒必要的公共變量以降低模塊間的耦合
度應該構(gòu)造僅有一個模塊或函數(shù)可以修改創(chuàng)建而其余有關(guān)模塊或函數(shù)只訪問的公共變
量防止多個不同模塊或函數(shù)都可以修改創(chuàng)建同一公共變量的現(xiàn)象
<規(guī)則3> 當向公共變量傳遞數(shù)據(jù)時要防止越界現(xiàn)象發(fā)生
對公共變量賦值時若有必要應進行合法性檢查以提高代碼的可靠性穩(wěn)定性
<規(guī)則4> 返回值為指針的函數(shù)不可將局部變量的地址作為返回值
當函數(shù)退出時非static局部變量將消失所以引用返回的指針將可能引起嚴重后果下例
將不能完成正確的功能
char *GetFilename(int nFileNo)
{
char szFileName[20];
sprintf( szFileName, "COUNT%d", nFileNo);
return szFileName;
}
<規(guī)則5> 盡量不設(shè)計多參數(shù)函數(shù)將不使用的參數(shù)從接口中去掉降低接口復雜度
減少函數(shù)間接口的復雜度
<規(guī)則6> 對所調(diào)用函數(shù)的返回碼要仔細全面地處理
防止把錯誤傳遞到后面的處理流程如有意不檢查其返回碼應明確指明
如
(void)fclose(fp);
<規(guī)則7> 顯示地給出函數(shù)的返回值類型無返回值函數(shù)定義為void
C C++語言的編譯系統(tǒng)默認無顯示返回值函數(shù)的返回值類型為int
<規(guī)則8> 聲明函數(shù)原型時給出參數(shù)名稱和類型并且與實現(xiàn)此函數(shù)時的參數(shù)名稱類型保持
一致無參數(shù)的函數(shù)用void聲明
示例下面聲明不正確
int CheckData( ) ;
int SetPoint( int, int ) ;
int SetPoint( x, y )
int x, y;
應改為如下聲明
int CheckData( void ) ;
int SetPoint( int x, int y ) ;
<規(guī)則9> 檢查接口函數(shù)所有輸入?yún)?shù)的有效性
可直接檢查或使用斷言進行檢查尤其是指針參數(shù)只在本模塊內(nèi)使用的函數(shù)可不
檢查
<規(guī)則10> 檢查函數(shù)的所有非參數(shù)輸入如數(shù)據(jù)文件公共變量等
可直接檢查或使用斷言進行檢查尤其是指針變量
<規(guī)則11> 聲明函數(shù)原型時對于數(shù)組型參數(shù)不要聲明為指針維護函數(shù)接口的清晰性
示例假設(shè)函數(shù)SortInt()完成的功能是對一組整數(shù)排序接受的參數(shù)是一整數(shù)數(shù)
-------------------------------------------------------------第 28 頁
組及數(shù)組中的元素個數(shù)以下聲明不符合規(guī)范
void SortInt(int num, int *data);
應聲明為
void SortInt(int num, int data[]);
2.6.代碼可測性
<規(guī)則1> 模塊編寫應該有完善的測試方面的考慮
<規(guī)則2> 源代碼中應該設(shè)計了代碼測試的內(nèi)容如打印宏開關(guān)變量值函數(shù)名稱函數(shù)值
等
在編寫代碼之前應預先設(shè)計好程序調(diào)試與測試的方法和手段并設(shè)計好各種調(diào)測開關(guān)及相
應測試代碼如打印函數(shù)等
程序的調(diào)試與測試是軟件生存周期中很重要的一個階段如何對軟件進行較全面高率的測
試并盡可能地找出軟件中的錯誤就成為很關(guān)鍵的問題因此在編寫源代碼之前除了要有一
套比較完善的測試計劃外還應設(shè)計出一系列代碼測試手段為單元測試集成測試及系統(tǒng)
聯(lián)調(diào)提供方便
<規(guī)則3> 在同一項目組或產(chǎn)品組內(nèi)要有一套統(tǒng)一的為集成測試與系統(tǒng)聯(lián)調(diào)準備的調(diào)測開關(guān)
及相應打印函數(shù)并且要有詳細的說明
本規(guī)則是針對項目組或產(chǎn)品組的
示例.ext文件示例文件名為EXAMPLE.EXT
/* 頭文件開始 */
#ifndef __EXAMPLE_EXT
#define __EXAMPLE_EXT
#define _EXAMPLE_DEBUG_ // 模塊測試總開關(guān)打開開關(guān)的含義是模塊可以
// 進行單元測試或其它功能目的等的測試
#ifdef _EXAMPLE_DEBUG_
#define _EXAMPLE_UNIT_TEST_ // 單元測試宏開關(guān)
#define _EXAMPLE_ASSERT_TEST_ // 斷言測試開關(guān)
... // 其它測試開關(guān)
#endif
#ifndef _EXAMPLE_UNIT_TEST_ // 若沒有定義單元測試
-------------------------------------------------------------第 29 頁
#include
#include
#ifndef _SYSTEM_DEBUG_VERSION_ // 如果是發(fā)行版本即非DEBUG版
#undef _EXAMPLE_UNIT_TEST_
#undef _EXAMPLE_ASSERT_TEST_
... // 將所有與測試有關(guān)的開關(guān)都關(guān)掉即編譯時不含任何測試代碼
#endif
#include
... // 其它接口頭文件
#else // 若定義了單元測試則應構(gòu)造單元測試所需的環(huán)境結(jié)構(gòu)等
typdef unsigned char _UC ;
typdef unsigned long _UL ;
#define TRUE 1
... // 所有為單元測試準備的環(huán)境如宏枚舉結(jié)構(gòu)聯(lián)合等
#endif
#endif /* EXAMPLE.EXT結(jié)束 */
/* 頭文件結(jié)束 */
<規(guī)則4> 在同一項目組或產(chǎn)品組內(nèi)調(diào)測打印出的信息串的格式要有統(tǒng)一的形式信息串中
至少要有所在模塊名或源文件名及行號
統(tǒng)一的調(diào)測信息格式便于集成測試
<規(guī)則5> 使用斷言來發(fā)現(xiàn)軟件問題提高代碼可測性
斷言是對某種假設(shè)條件進行檢查可理解為若條件成立則無動作否則應報告 它可以快
速發(fā)現(xiàn)并定位軟件問題同時對系統(tǒng)錯誤進行自動報警斷言可以對在系統(tǒng)中隱藏很深用
其它手段極難發(fā)現(xiàn)的問題進行定位從而縮短軟件問題定位時間提高系統(tǒng)的可測性實際
應用時可根據(jù)具體情況靈活地設(shè)計斷言
示例下面是C語言中的一個斷言用宏來設(shè)計的其中NULL為0L
#ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
void ExamAssert( char * szFileName, unsigned int nLineNo )
{
printf( "\n[EXAM] Assert failed: %s, line %u\n",
szFileName, nLineNo ) ;
abort( ) ;
}
#define EXAM_ASSERT( condition ) if ( condition )
\ //
若條件成立則無動作
NULL ; else \ //
否則報告
ExamAssert( __FILE__, __LINE__ )
#else // 若不使用斷言測試
#define EXAM_ASSERT( condition ) NULL
#endif /* ASSERT結(jié)束 */
<規(guī)則6> 用斷言來檢查程序正常運行時不應發(fā)生但在調(diào)測時有可能發(fā)生的非法情況
<規(guī)則7> 不能用斷言代替錯誤處理來檢查最終產(chǎn)品肯定會出現(xiàn)且必須處理的錯誤情況
如某模塊收到其它模塊或鏈路上的消息后要對消息的合理性進行檢查此過程為正常的錯
誤檢查不能用斷言來代替
評論