1. 背景
無論如何,標(biāo)準(zhǔn)是團隊開發(fā)的保證之一,而且標(biāo)準(zhǔn)歡迎爭吵和變化。我們使代碼易于管理的方法之一是增強代碼一致性,讓別人可以讀懂你的代碼是很重要的,保持統(tǒng)一編程風(fēng)格意味著可以輕松根據(jù)“模式匹配”規(guī)則推斷各種符號的含義。創(chuàng)建通用的、必需的習(xí)慣用語和模式可以使代碼更加容易理解,在某些情況下改變一些編程風(fēng)格可能會是好的選擇,但我們還是應(yīng)該遵循一致性原則,盡量不這樣去做。
2. 環(huán)境
統(tǒng)一開發(fā)人員的開發(fā)環(huán)境,包括文本編輯環(huán)境、SHELL環(huán)境,通常我們使用VIM的UTF-8編碼環(huán)境,使用4個空格代替Tab進行縮進。
3. 命名
頭文件(.h 文件)和程序文件(.c文件)文件名全部使用小寫字母或數(shù)字,以下劃線(_)進行分隔,且盡量保證頭文件和程序文件的一一對應(yīng)。
即: ngm_模塊名_類別名
1 2 3 | #示例: ngm_cstring.h ngm_cstring.c |
4. 編碼
4.1 頭文件
4.1.1 #define的保護
頭文件使用 #define 防止頭文件被多重包含。
即:_Project_Path_File_H_
注:為保證唯一性,頭文件的命名應(yīng)基于其所在項目源代碼樹的絕對路徑。
1 2 3 4 5 | #示例: #ifndef _NGM_CSTRING_H_ #define _NGM_CSTRING_H_ ...... #endif |
4.1.2 頭文件依賴
使用前置聲明盡量減少.h文件中#include的數(shù)量。
當(dāng)一個頭文件被包含的同時也引入了一項新的依賴,只要該頭文件被修改,代碼就要重新編譯。如果你的頭文件包含了其他頭文件,這些頭文件的任何改變也將導(dǎo)致那些包含了你的頭文件的代碼重新編譯。因此,我們寧可盡量少包含頭文件,尤其是那些包含在其他頭文件中的。
注:對于內(nèi)部調(diào)用的函數(shù),即使定義到新的程序文件中,但其聲明在調(diào)用此函數(shù)的程序文件開頭,除非需要將此內(nèi)部函數(shù)作為公共接口,對于同一個程序文件中的內(nèi)部調(diào)用函數(shù)提倡使用static進行修飾,防止外部的錯誤調(diào)用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #示例: //file a.h #include struct a_t { char a; }; //file a.c int b_func(...); int a_func(...) { statment; b_func(); } //file b.c int b_func(...) { statment; } |
4.1.3 函數(shù)說明
采用doxygen生成幫助文檔,遵循doxygen文件說明格式。
1. 每個 .h 文件必須添加注釋,包括頭部和函數(shù)聲明。
1 2 3 4 5 6 7 8 9 | #示例: /** * @file a.h * 描述:XXXX */ /** * a_func函數(shù)功能說明 */ int a_func( int a, char *b); |
注:由于函數(shù)的詳細說明寫在函數(shù)實現(xiàn)部分,所以在頭文件中定義不強制要求詳細說明,只需描述函數(shù)的基本功能。
2. 每個 .c 文件必須添加注釋,包括頭部和函數(shù)定義。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #示例: /** * @file a.c * 描述: XXXX */ /** * @brief a_func函數(shù)功能說明 * @param[in] a 參數(shù)a說明 * @param[in] b 參數(shù)b說明 * @return 返回值說明 * @notes 注意事項說明 */ int a_func( int a, char *b) { ...... } |
3. 代碼中關(guān)鍵部分必須添加注釋。
4. 將代碼按照模塊進行分類。
定義一個組的方法有:@defgroup 組id(唯一的),組描述;@addtogroup也是創(chuàng)建組,兩者的區(qū)別是addtogroup的組id如果不存在,則自動創(chuàng)建,如果存在,則加入改組;@ingroup 組id 屬于某個組。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 示例: /** * @file ngm_doxygen.h * @brief ngm modules define * @remark 此頭文件必須包含在 */ 定義core模塊 /** * @defgroup ngm_core * @{ */ /** * @} */ 需要加入 ngm_core 組的文件中增加 /** * @addtogroup ngm_core * @{ */ ...... /** * @} */ |
4.1.4 函數(shù)參數(shù)順序
定義參數(shù)時,參數(shù)順序為:類對象、輸入?yún)?shù)、輸出參數(shù)。
4.2 程序文件
4.2.1 函數(shù)定義的描述
函數(shù)定義的描述使用doxygen格式進行詳細描述。
4.2.2 函數(shù)定義的風(fēng)格
函數(shù)定義采用ASNI C方式,即函數(shù)參數(shù)在圓括號內(nèi)進行類型定義。避免使用 K&R 方式,同時確保聲明和定義格式統(tǒng)一。
1 2 3 4 5 | 示例: int func( int a, char *b) { ...... } |
4.2.3 全局變量與局部變量
程序庫中通常禁止定義全局變量,盡量使用函數(shù)參數(shù)傳遞的方式進行變量值的共享。
4.2.4 靜態(tài)函數(shù)(供內(nèi)部調(diào)用)
程序文件中供內(nèi)部調(diào)用的函數(shù)(非公共接口函數(shù)),盡量使用static進行內(nèi)部聲明的修飾,防止外部程序的錯誤調(diào)用。
4.2.5 包含頭文件順序
我們通常采用:(a)系統(tǒng)頭文件;(b)標(biāo)準(zhǔn)頭文件;(c)第三方頭文件;(d)自己的頭文件
4.2.6 編寫短小函數(shù)
傾向于選擇短小、精煉的函數(shù)。長函數(shù)有時是恰當(dāng)?shù)?,因此對于函?shù)長度并沒有嚴格限制。如果函數(shù)超過80行,可以考慮在不影響程序結(jié)構(gòu)的情況下將其分割一下。即使一個長函數(shù)現(xiàn)在工作的非常好,一旦有人對其修改,有可能出現(xiàn)新的問題,甚至導(dǎo)致難以發(fā)現(xiàn)的bugs。使函數(shù)盡量短小、簡單,便于他人閱讀和修改代碼。在處理代碼時,你可能會發(fā)現(xiàn)復(fù)雜的長函數(shù),不要害怕修改現(xiàn)有代碼:如果證實這些代碼使用、調(diào)試困難,或者你需要使用其中的一小塊,考慮將其分割為更加短小、易于管理的若干函數(shù)。分割函數(shù)時需要注意一下,不要使函數(shù)嵌套太深,這樣不利于閱讀,定位bug也不方便。
5. 注釋
5.1 注釋風(fēng)格
統(tǒng)一使用 /* …… */,并且注釋語言統(tǒng)一使用中文。
5.2 函數(shù)注釋
5.2.1 函數(shù)聲明注釋
函數(shù)聲明只描述函數(shù)的基本功能,詳細描述統(tǒng)一放置于函數(shù)定義中。
5.2.2 函數(shù)定義注釋
函數(shù)定義描述函數(shù)詳細說明,以及函數(shù)功能和實現(xiàn)要點。如使用的漂亮代碼、實現(xiàn)的簡要步驟、如此實現(xiàn)的理由、為什么前半部分要加鎖而后半部分不需要。函數(shù)定義處注釋的基本內(nèi)容:
(1) inputs(輸入)及outputs(輸出);
(2) 函數(shù)內(nèi)部是否分配了空間,是否需要由調(diào)用者釋放;
(3) 參數(shù)是否可以為NULL或其他返回值;
(4) 是否存在函數(shù)使用的性能隱憂(performance implications);
(5) 如果函數(shù)是可重入的(re-entrant),其同步前提(synchronization assumptions)是什么?
5.3 變量注釋
通常變量名本身足以很好說明變量用途,特定情況下,需要額外注釋說明。
注:對于業(yè)務(wù)相關(guān)變量盡量加上注釋。
5.4 實現(xiàn)注釋
對于實現(xiàn)代碼中巧妙的、晦澀的、有趣的、重要的地方加以注釋。
5.5. 單行及多行注釋
采用單行或多行注釋,置于實現(xiàn)代碼句(塊)的上方。
注:變量聲明的注釋可置于變量后方。
1 2 3 4 5 6 7 8 9 10 11 | #示例: if (condition) { int num_errors; /* 內(nèi)部錯誤計數(shù) */ /* 以下代碼的運行注釋 */ ...... /** * 以下代碼的運行注釋 * XXXX */ ...... } |
6. 格式
6.1 行長度
一行不應(yīng)該超過 80 列,如果超過 80 列了,想辦法折斷語句。
6.2 空格還是制表符
對于縮進是一直引起爭議的,我們將tab設(shè)置為4個空格,程序內(nèi)部的縮進每次使用一個Tab(即4個空格)。
6.3 函數(shù)與聲明
返回類型和函數(shù)名在同一行,合適的話,參數(shù)也放在同一行。
6.4 花括號的爭議
即使是最優(yōu)秀的程序員也會在這個地方引起劇烈爭論,就是 {} 規(guī)則。
(1) 這里明確指出,兩種方法(即 K&R 或 BSD)都是可以接受的,但是你不能在一個文件中引入新的風(fēng)格或混用兩種風(fēng)格。
(2) if, else, while/do, for 等之后, 總是使用 {} 包括起來,即使只有一個語句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #示例: #(1)Apache風(fēng)格 if (condition) { ...... } else { ...... } #(2)MySQL風(fēng)格 if (condition) { ...... } else { ...... } #(3)PHP風(fēng)格 if (condition) { ...... } else { ...... } |
6.5 函數(shù)名后面括號的爭議
不要在函數(shù)名和括號之間保留空格。
1 2 3 4 | #不推薦: func () #推薦: func() |
6.6 條件/循環(huán)/開關(guān)語句
(1) if/else, while/do, switch/case 等操作符之后留一個空格。
(2) 更提倡不在圓括號中添加空格。
(3) 通常情況下右圓括號和左大括號(如果使用的話)間也要有個空格
1 2 3 4 | #不推薦: if (condition); for (;;); #推薦: if (condition); for (;;); |
6.7 逗號和分號后留有空格
逗號和分號后面要留有空格。
1 2 3 4 | #不推薦: func( int a, char *b); for (i=0;i<n;i++); #推薦: func( int a, char *b); for (i = 0; i < n; i++); |
6.8 一元操作符前后留有空格
= 、+ 、-、/、 * 等一元操作符前后留有空格
1 2 3 4 | #不推薦: var=a*5; #推薦: var = a * 5; |
6.9 條件表達式
(1) 通常將常量值置于 == 的左方,避免程序書寫錯誤導(dǎo)致成為賦值語句。
(2) 比較時的常量值采用:整數(shù)用0,實數(shù)用0.0,指針用NULL,字符(串)用’\0′。
(3) 盡量避免在條件表達式中使用 ! 操作符。例如: if (!p) {};
(4) 邏輯與&&(邏輯或||)操作符總是位于行首,避免一行多于80個字符。
7. 聲明
7.1 每行聲明的變量數(shù)量
推薦一行一個變量。因為這樣以利于寫注釋,此時注釋可以寫在變量后面或是上方。
7.2 初始化
盡量在聲明局部變量的同時初始化。唯一不這么做的理由是變量的初始值依賴于某些先前發(fā)生的計算。
1 2 3 4 5 6 7 8 9 10 11 12 | #示例: #整數(shù)初始化: int i = 0; #字符數(shù)組初始化: char buf[32] = {0}; #指針初始化: char *ptr = NULL; #結(jié)構(gòu)初始化: 采用init或 new 的方式。 |
7.3 變量聲明布局
只在函數(shù)定義(或是代碼塊)的開始處聲明變量。避免在函數(shù)中間或首次使用變量時進行聲明,雖然C99支持這種聲明。
8. 語句
8.1 簡單語句
同一行內(nèi)只寫一行語句,使用分號結(jié)束。
1 2 3 4 5 | #不推薦: i++; j++; #推薦: i++; j++; |
8.2 復(fù)合語句
盡量避免使用嵌套的復(fù)合語句。
1 2 3 4 5 | #不推薦: if (NULL == (fp = fopen (filename, "r" ))) #推薦: fp = fopen (filename, "r" ); if (NULL == fp) |
8.3 返回語句
return表達式中不要使用圓括號。
8.4 條件運算符
盡量避免使用條件運算符(? :)。
8.5 條件語句
if-else條件語句總是使用 {} 花括號括起來,即使只有一條語句,并且避免else的懸掛。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #示例: if (condition) { /* 注釋 */ …… } else if (condition) { /* 注釋 */ …… } else { /* 注釋 */ …… } |
8.6 循環(huán)語句
8.6.1 for循環(huán)語句
(1) 循環(huán)體使用花括號 {} 括起來,并且設(shè)置initialization,condition,update。
(2) 空循環(huán)體應(yīng)使用花括號 {} 或 continue,而不是一個簡單的分號。
8.6.2 while/do-while循環(huán)語句
(1) 循環(huán)體使用花括號 {} 括起來,并且設(shè)置 condition 條件.
(2) 對于true 或 1的真條件進行注釋,同時注意條件值的增長。
(3) 空循環(huán)體應(yīng)使用花括號 {} 或 continue,而不是一個簡單的分號。
8.7 開關(guān)選擇語句
(1) switch語句可以使用大括號分塊;空循環(huán)體應(yīng)使用{}或continue。
(2) 每個case必須以break結(jié)束,并且switch 的 default case 總應(yīng)該存在。
8.8 自增/自減操作符
盡量避免自增/自減操作的嵌套賦值。統(tǒng)一只用后置自增自減操作符(除非程序特殊需要),因為目前的智能編譯器對于前置和后置的操作符已經(jīng)可以生成同樣效率的匯編代碼。
8.9 sizeof操作符
盡可能使用sizeof(varname)代替sizeof(type)。使用sizeof(varname)是因為當(dāng)變量類型改變時代碼自動同步,有些情況下sizeof(type)或許有意義,還是要盡量避免,如果變量類型改變的話不能同步。
8.10 const的使用
在函數(shù)聲明時,對于輸入變量參數(shù)盡量使用const加以修飾。
9. 空白與空行
首要原則:水平留白的使用因地制宜,不要在行尾添加無謂的留白,空行越少越好。
空行的目的:將邏輯相關(guān)的代碼段分隔開,以提高可讀性。這不僅僅是規(guī)則而是原則問題了:不是非常有必要的話就不要使用空行。尤其是:不要在兩個函數(shù)定義之間空超過2行,函數(shù)體頭、尾不要有空行,函數(shù)體中也不要隨意添加空行。
基本原則是:同一屏可以顯示越多的代碼,程序的控制流就越容易理解。當(dāng)然,過于密集的代碼塊和過于疏松的代碼塊同樣難看,取決于你的判斷,但通常是越少越好。
10. 函數(shù)、變量命名規(guī)則
最重要的一致性規(guī)則是命名管理,命名風(fēng)格直接可以直接確定命名實體是:類型、變量、函數(shù)、常量、宏等等,無需查找實體聲明,我們大腦中的模式匹配引擎依賴于這些命名規(guī)則。
命名規(guī)則具有一定隨意性,但相比按個人喜好命名,一致性更重要,所以不管你怎么想,規(guī)則總歸是規(guī)則
函數(shù)命名、變量命名、文件命名應(yīng)具有描述性,不要過度縮寫,類型和變量應(yīng)該是名詞,函數(shù)名可以用“命令性”動詞。
10.1 函數(shù)
函數(shù)名全部采用小寫字母或數(shù)字,使用下劃線(_)進行分隔。
即:ngm_模塊名_類別(功能)名_操作名
1 2 | 示例: ngm_mime_header_parse(...); |
10.2 變量
變量名一律小寫,單詞間以下劃線相連,盡量減少縮寫。
基本數(shù)據(jù)類型定義的變量通常采用具有明確意義的單詞或是縮寫,系統(tǒng)typedef聲明的數(shù)據(jù)類型定義的變量通常采用類型縮寫作為前綴,盡量做到變量名的清晰和簡單。
變量命名約定:
(1) 結(jié)構(gòu)類型:以結(jié)構(gòu)簡寫開頭,如:bf_dispname
(2) 靜態(tài)變量:以 s_ 開頭
(3) 全局變量:以 g_ 開頭
10.3 常量
const常量在名稱前加c:c_username。
10.4 結(jié)構(gòu)
所有結(jié)構(gòu)體以typedef進行定義,并采用 *_t 的結(jié)構(gòu)體標(biāo)識方式。
1 2 3 4 | #示例: typedef struct _ngm_cstring_t { ...... } ngm_cstring_t; |
10.5 枚舉
枚舉值應(yīng)全部為大寫字母或數(shù)字,單詞間以下劃線相連。枚舉名采用駱駝命名方式。
1 2 3 4 5 | #示例: enum NgmTableErrors { NGM_RET_OK; NGM_RET_ERROR; }; |
10.6 宏定義
宏定義應(yīng)全部為大寫字母或數(shù)字,單詞以下劃線相連。
1 2 3 | #示例: NGM_RET_SUCCESS 0; NGM_RET_FAILED -1; |
聯(lián)系客服