0規範制定說明
0.1箴言
技術人員設計程序的首要目的是用於技術人員溝通和交流,其次纔是用於機器執行。程序的生命力在於用戶使用,程序的成長在於後期的維護及根據用戶需求更新和升級功能。如果你的程序只能由你來維護,當你離開這個程序時,你的程序也和你一起離開了,這將給公司和後來接手的技術人員帶來巨大的痛苦和損失。因此,爲了程序可讀、易理解、好維護,你的程序需要遵守一定的規範,你的程序需要設計。
“程序必須爲閱讀它的人而編寫,只是順便用於機器執行。”
——Harold Abelson 和 Gerald Jay Sussman
“編寫程序應該以人爲本,計算機第二。”
——Steve McConnell
0.2簡介:
爲提高產品代碼質量,指導儀表嵌入式軟件開發人員編寫出簡潔、可維護、可靠、可測試、高效、可移植的代碼,編寫了本規範。
本規範將分爲完整版和精簡版,完整版將包括更多的樣例、規範的解釋以及參考材料(what & why),而精簡版將只包含規則部分(what)以便查閱。
在本規範的最後,列出了一些業界比較優秀的編程規範,作爲延伸閱讀參考材料。
本規範主要包含以下兩個方面的內容:
一:爲形成統一編程規範,從編碼形式角度出發,本規範對標示符命名、格式與排版、註釋等方面進行了詳細闡述。
二:爲編寫出高質量嵌入式軟件,從嵌入式軟件安全及可靠性出發,本規範對由於C語言標準、C語言本身、C編譯器及個人理解導致的潛在危險進行說明及規避。
0.3適用範圍:
本規範適用於濟南金鐘電子衡器股份有限公司儀表檯秤產品部嵌入式軟件的開發,也對其他嵌入式軟件開發起一定的指導作用。
0.4術語定義
0.4.1 規範術語
原則:編程時必須堅持的指導思想。
規則:編程時需要遵循的約定,分爲強制和建議(強制是必須遵守的,建議是一般情況下需要遵守,但沒有強制性)。
說明:對原則/規則進行必要的解釋。
實例:對此原則/規則從正、反兩個方面給出例子。
材料:擴展、延伸的閱讀材料。
Unspecified:未詳細說明的行爲,這些是必須成功編譯的語言結構,但關於結構的行爲,編譯器的編寫者有某些自由。例如C語言中的“運算次序”問題。這樣的問題有 22 個。 在某種方式上完全相信編譯器的行爲是不明智的。編譯器的行爲甚至不會在所有可能的結構中都是一致的。
Undefined:未定義行爲,這些是本質的編程錯誤,但編譯器的編寫者不一定爲此給出錯誤信息。相應的例子是無效參數傳遞給函數,或函數的參數與定義時的參數不匹配。從安全性角度這是特別重要的問題,因爲它們代表了那些不一定能被編譯器捕捉到的錯誤。
Implementation-defined:實現定義的行爲,這有些類似於“unspecified ”問題,其主要區別在於編譯器要提供一致的行爲並記錄成文檔。換句話說,不同的編譯器之間功能可能會有不同,使得代碼不具有可移植性,但在任一編譯器內,行爲應當是良好定義的。比如用在一個正整數和一個負整數上的整除運算“/ ”和求模運算符“% ”。存在76個這樣的問題。從安全性角度,假如編譯器完全地記錄了它的方法並堅持它的實現,那麼它可能不是那樣至關重要。儘可能的情況下要避免這些問題。
0.4.2 C語言相關術語
聲明(declaration):指定了一個變量的標識符,用來描述變量的類型,是類型還是對象,
者函數等。聲明,用於編譯器(compiler)識別變量名所引用的實體。以下這些就是聲明:
extern int bar;
extern int g(int,int);
double f(int,double);[對於函數聲明,extern關鍵字是可以省略的。]
定義(definition):是對聲明的實現或者實例化。連接器(linker)需要它(定義)來引用內存實體。與上面的聲明相應的定義如下:
int bar;
int g(int lhs,int rhs) {returnlhs*rhs;}
double f(int i,double d) {returni+d;}
0.5規則的形式
規則/原則<序號>(規則類型):規則內容。
[原始參考]
<序號>:每條規則都有一個序號,序號是按照章節目錄-**的形式,從數字1開始。例如,若在此章節有個規則的話,序號爲0.5-1。
(規則類型):或者是‘強制’,或者是‘建議’。
規則內容:此條規則的具體內容。
[原始參考]:指示了產生本條款或本組條款的可應用的主要來源。
1標示符命名規則
1.1標示符命名總則
規則1.1-1(強制):標識符(內部的和外部的)的有效字符不能多於31。
[UndefinedImplementation-defined]
說明:ISO 標準要求在內部標識符之間前31 個字符必須是不同的,外部標識符之間前6 個字符必須是不同的(忽略大小寫)以保證可移植性。我們這裏放寬了此要求,要求內部、外部標示符的有效字符不能多於31即可。這樣主要是便於編譯器識別,代碼清晰易讀,並保證可移植性。
規則1.1-2(強制):具有內部作用域的標識符不應使用與具有外部作用域的標識符相同的
名稱,在內部作用域裏具有內部標示符會隱藏外部標識符。
說明:外部作用域和內部作用域的定義如下。文件範圍內的標識符可以看做是具有最外部
(outermost )的作用域;塊範圍內的標識符看做是具有更內部(more inner)的作用域,連續嵌套的塊,其作用域更深入。如果內部作用域標示符和外部作用域標示符同名,內部作用域標示符會覆蓋外部作用域標示符,導致程序混亂。
實例:
INT8U test;
{
INT8U test; /*定義了兩個test */
test = 3; /*這將產生混淆 */
}
規則1.1-3(建議):具有靜態存儲期的對象或函數標識符不能重用。
說明:不管作用域如何,具有靜態存儲期的標識符都不應在系統內的所有源文件中重用。它包含帶有外部鏈接的對象或函數,及帶有靜態存儲類標識符的任何對象或函數。在一個文件中存在一個具有內部鏈接的標識符,而在另外一個文件中存在着具有外部鏈接的相同名字的標識符,或者存在兩個標示符相同的外部標示符。對用戶來說,這有可能導致混淆。
實例:
test1.c
/**定義了一個靜態文件域變量test1*/
static INT8U test1;
void test_fun(void)
{
INT8U test1; /*定義了一個同名的局部變量test1*/
}
test2.c
/**在另一個文件又定義了一個具有外部鏈接的文件域變量test1*/
INT8U test1;
原則1.1-4(強制):標識符的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
說明:標示符的命名儘量做到見名知意,儘量讓別人快速理解你的代碼。
實例:
好的命名方法:
INT8U debug_message;
INT16U err_num;
不好的命名方法:
INT8U dbmesg;
INT16U en;
原則1.1-5(強制):常見通用的單詞縮寫儘量統一,不得使用漢語拼音、英語混用。
說明:簡短的單詞可以使用略去‘元音’字母形成縮寫,較長的單詞可以使用音節首字母
者單詞前幾個字母形成縮寫,針對大家公認的單詞縮寫要統一。對於特定的項目要使
用的專有縮寫應該註明或者做統一說明。
實例:常見單詞縮寫表(建議):
單詞 | 縮寫 | 單詞 | 縮寫 |
argument | arg | buffer | buf |
clock | clk | command | cmd |
compare | cmp | configuration | cfg |
device | dev | error | err |
hexadecimal | hex | increment | inc |
initialize | init | maximum | max |
message | msg | minimum | min |
parameter | param | previous | prev |
register | reg | semaphore | sem |
statistic | stat | synchronize | syn |
temp | tmp |
|
|
原則1.1-6(建議):用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等。
實例:常見反義詞表:
正義 | 反義 | 正義 | 反義 |
add | remove | begin | end |
create | destroy | insert | delete |
first | last | get | release |
increment | decrement | put | get |
add | delete | lock | unlock |
open | close | min | max |
old | new | start | stop |
next | previous | source | target |
show | hide | send | receive |
source | destination | copy | pase |
up | down |
|
|
原則1.1-7(建議):標示符儘量避免使用數字編號,除非邏輯上需要。
實例:
#define DEBUG_0_MSG
#define DEBUG_1_MSG
應改爲更有意義的定義:
#define DEBUG_WARN_MSG
#define DEBUG_ERR_MSG
材料:《代碼大全第2版》(Steve McConnell 著 金戈/湯凌/陳碩/張菲 譯 電子工業出版社
2006年3月)"第11章變量命的力量"。
1.2文件命名及存儲規則
規則1.2-1(強制):文件名使用小寫字母。
說明:由於不同系統對文件名大小寫處理不同,Windows不區分文件名大小寫,而Linux區分。所以文件名命名均採用小寫字母,多個單詞之間可使用”_”分隔符。
實例:disp.h os_sem.c
規則1.2-2(建議):工程源碼使用GB2312編碼方式。
說明:程序裏的註釋可能會使用中文,GB2312是簡體中文編碼,大部分的編輯工具和集成IDE環境都支持GB2312編碼,爲避免中文亂碼,建議使用GB2312對源碼進行編碼。若需要轉換成其他編碼格式,可使用文本編碼轉換工具進行轉換。
規則1.2-3(強制):工程源碼使用版本管理工具進行版本管理。
說明:程序一般需要大量更新、修正、維護工作,且有時需要多人合作。使用版本管理工具可以幫助你提高工作效率。建議使用“Git”版本管理工具。
1.3變量命名規則
原則1.3-1(強制):變量命名應明確所代表的含義或者狀態。
說明:變量名稱可以使用名詞表述清楚的儘量使用名詞,使用名詞無法描述清楚時,使用形
容詞或者描述性的單詞+名詞的形式。變量一般爲實體的屬性、狀態等信息,使用上
述方案一般可以解決變量名的命名問題,如果出現命名很困難或者無法給出合理的命
名方式時,問題可能出現在整體設計上,請重新審視設計。
規則1.3-2(強制):全局變量添加”G_”前綴,全局靜態變量添加” S_ ”,局部靜態變量添加”s_”前綴。使用大小寫混合方式命名,大寫字母用於分割不同單詞。
說明:添加前綴的原因有兩個。首先,使全局變量變得更醒目,提醒技術開發人員使用這些變量時要小心。其次,添加前綴使全局變量和靜態變量變得和其他變量不一致,提醒技術開發人員儘量少用全局變量。
實例:
/**出錯信息 */
INT8U G_ErrMsg;
/**每秒鐘轉動圈數 */
static INT32U S_CirclePerSec;
規則1.3-3(強制):局部變量使用小寫字母,若標示符比較複雜,使用’_’分隔符。
說明:局部變量全部使用小寫字母,和全局變量有明顯區分,使讀者看到標示符就知道是何
種作用域的變量。
實例:
INT32U download_program_address;
規則1.3-4(強制):定義指針變量*緊挨變量名,全局指針變量使用大寫P前綴”P_”,局部指針變量使用小寫p前綴”p _”。
實例: INT8U *P_MsgAddress; /*全局變量*/
INT8U *p_msg; /*局部變量*/
1.4函數命名規則
原則1.4-1(強制):函數命名應該明確針對什麼對象做出了什麼操作。
說明:函數的功能是獲取、修改實體的屬性、狀態等,採用“動詞+名詞”的方式可以滿足上述需求,若出現使用此方式命名函數很困難或不能命名的情況,問題可能出現在整體設計上,請重新審視設計方案。
規則1.4-2(強制):具有外部鏈接的函數命名使用大小寫混合的方式,首字母大寫,用於分割不同單詞。
說明:函數具有外部鏈接屬性的含義是函數通過頭文件對外聲明後,對其他文件或模塊來
說是可見的。如果一個函數要在其他模塊或者文件中使用,需要在頭文件中聲明該函
數。另外,在頭文件聲明函數,還可以促使編譯器檢查函數聲明和調用的一致性。
實例:
char *GetErrMsg(ErrMsg *msg);
規則1.4-3(強制):具有文件內部鏈接屬性的函數命名使用小寫字母,使用’_’分隔符分割不同單詞,且使用static關鍵字限制函數作用域。
說明:函數具有內部鏈接屬性的含義是函數只能在模塊或文件內部調用,對文件或模塊外來
說是不可見的。如果一個函數僅在模塊內部或者文件內部使用,需要限制函數鏈接圍,
使用static修飾符修飾函數,使其只具有內部鏈接屬性。在源文件中聲明一遍具有內
部鏈接的函數同樣具有促使編譯器檢查函數聲明和調用的一致性。
實例:
static char get_key(void);
規則1.4-4(強制):函數參數使用小寫字母,各單詞之間使用“_”分割,儘量保持參數順序從左到右爲:輸入、修改、輸出。
說明:函數參數順序爲需輸入參數值(這個值一般不修改,若不需要修改使用const關鍵字
修飾),需修改的參數(這個參數輸入後用於提供數據,函數內部可以修改此參數),
輸出參數(這個參數是函數輸出值)。
1.5常量的命名規則
規則1.5-1(強制):常量(#define定義的常量、枚舉、const定義的常量)的定義使用全大寫字母,單詞之間加 ’_’分割的命名方式。
實例:
#define PI_ROUNDED 3.14
const double PI_ROUNDED = 3.14;
enum weekday{ SUN,MON,TUE,WED,THU,FRI,SAT };
規則1.5-2(建議):常數宏定義時,十六進制數的表示方法爲0xFF。
說明:前面0x中的x小寫,數據中的”A-F”大寫。
1.6新定義的類型命名規範
規則1.6-1(強制):新定義類型名的命名應該明確抽象對象的含義,新類型名使用大寫字母,單詞之間加’_’分割,新類型指針在類型名前增加前綴”P_”。成員變量標示符前加類型名稱前綴,首字母大寫用於區分各個單詞。
實例:typedef struct _STUDENT
{
StudentName;
StudentAge ;
......
}STUDENT , *P_ STUDENT;/* STUDENT 爲新類型名稱,P_ STUDENT 爲新類型指針名*/
2外觀佈局
2.1排版與格式
2.1.1 頭文件排版
規則2.1.1-1(強制):頭文件排版內容依次爲包含的頭文件、宏定義、類型定義、聲明變量、聲明函數。且各個種類的內容間空三行。
說明:頭文件是模塊對外的公用接口。在頭文件中定義的宏,可以被其他模塊引用。Project
中不建議使用全部變量,若使用則需在頭文件裏對外聲明。模塊對外的函數接口在模
塊頭文件裏聲明。
2.1.2 源文件排版
規則2.1.2-1(強制):源文件排版內容依次爲包含的頭文件、宏定義、具有外部鏈接屬性的全局變量定義、模塊內部使用的static變量、具有內部鏈接的函數聲明、函數實現代碼。且各個種類的內容間空三行。
說明:模塊內部定義的宏,只能在該模塊內部使用。只在模塊內部使用的函數,需在源碼文
件中聲明,用於促使編譯器檢查函數聲明和調用的一致性。
規則2.1.2-2(強制):程序塊採用縮進風格編寫,每級縮進4個空格。
說明:當前主流IDE都支持Tab縮進,使用Tab縮進需要打開和設置相關選項。宏定義、編譯開關、條件預處理語句可以頂格。
規則2.1.2-3(強制):if、for、do、while、case、switch、defaul、typedef等語句獨佔一行,且這些關鍵字後需空一格。
說明:執行語句必須用縮進風格寫,屬於if、for、do、while、case、switch、default、typedef
等的下一個縮進級別。一般寫if、for、do、while等語句都會有成對出現的{}