對於不同的編程語言來說,具體的編碼規范可以有很大的不同,但是其宗旨都是一致的,就是保證代碼在高質量完成需求的同時具備良好的可讀性、可維護性。例如我們可以規定某個項目的C語言程序要遵循這樣的規定:變量的命名,頭文件的書寫和#include
等等。
下面是一些廣為采用的編碼規范:
GNU Coding Standards Guidelines for the Use of the C Language in Vehicle Based Software C++ Coding Guidelines SUN Code Conventions for Java以下是一些介紹編碼、編碼規范的書籍:
C++編碼規范,陳世忠,人民郵電出版社,2002 高質量程序設計指南:C++/C語言,林銳等,電子工業出版社,2003注:以下只是根據課題組已有的經驗給出的總結,並非對所有場景均適用。
對於高質量的工程,一般會做到:
代碼簡潔精煉,美觀,可讀性好,高效率,高復用,可移植性好,高內聚,低耦合,沒有冗余,不符合這些原則,必須特別說明。 規范性,代碼有規可循。特殊排版、特殊語法、特殊指令,必須特別說明。一、文件排版方面
包含頭文件
1.1 先系統頭文件,後用戶頭文件。
1.2 系統頭文件,穩定的目錄結構,應采用包含子路徑方式。
1.3 自定義頭文件,不穩定目錄結構,應在dsp中指定包含路徑。
1.4 系統頭文件應用:#include
1.5 自定義同文件應用:#include "xxx.h"
1.6 只引用需要的頭文件。
h和cpp文件
2.1 頭文件命名為*.h
,內聯文件命名為*.inl
;C++文件命名為*.cpp
2.2 文件名用大小寫混合,或者小寫混合。例如DiyMainView.cpp
,infoview.cpp
。不要用無意義的名稱:例如XImage.cpp
;SView.cpp
;xlog.cpp
;
2.3 頭文件除了特殊情況,應使用#ifdef
控制塊。
2.4 頭文件#endif
應采用行尾注釋。
2.5 頭文件,首先是包含代碼塊,其次是宏定義代碼塊,然後是全局變量,全局常量,類型定義,類定義,內聯部分。
2.6 CPP文件,包含指令,宏定義,全局變量,函數定義。
文件結構
3.1 文件應包含文件頭注釋和內容。
3.2 函數體類體之間原則上用2個空行,特殊情況下可用一個或者不需要空行。
空行
4.1 文件頭、控制塊,#include
部分、宏定義部分、class
部分、全局常量部分、全局變量部分、函數和函數之間,用兩個空行。
二、注釋方面
文件頭注釋
1.1 作者,文件名稱,文件說明,生成日期(可選)
函數注釋
2.1 關鍵函數必須寫上注釋,說明函數的用途。
2.2 特別函數參數,需要說明參數的目的,由誰負責釋放等等。
2.3 除了特別情況,注釋寫在代碼之前,不要放到代碼行之後。
2.4 對每個#else
或#endif
給出行末注釋。
2.5 關鍵代碼注釋,包括但不限於:賦值,函數調用,表達式,分支等等。
2.6 善未實現完整的代碼,或者需要進一步優化的代碼,應加上 // TODO ...
2.7 調試的代碼,加上注釋 // only for DEBUG
2.8 需要引起關注的代碼,加上注釋 // NOTE ...
2.9 對於較大的代碼塊結尾,如for,while,do
等,可加上 // end for|while|do
三、命名方面
原則
1.1 同一性:在編寫一個子模塊或派生類的時候,要遵循其基類或整體模塊的命名風格,保持命名風格在整個模塊中的同一性。
1.2 標識符組成:標識符采用英文單詞或其組合,應當直觀且可以拼讀,可望文知意,用詞應當准確,避免用拼音命名。
1.3 最小化長度 && 最大化信息量原則:在保持一個標識符意思明確的同時,應當盡量縮短其長度。
1.4 避免過於相似:不要出現僅靠大小寫區分的相似的標識符,例如"i"
與"I"
,"function"
與"Function"
等等。
1.5 避免在不同級別的作用域中重名:程序中不要出現名字完全相同的局部變量和全局變量,盡管兩者的作用域不同而不會發生語法錯誤,但容易使人誤解。
1.6 正確命名具有互斥意義的標識符:用正確的反義詞組命名具有互斥意義的標識符,如:"nMinValue"
和 "nMaxValue"
,"GetName()"
和 "SetName()"
….
1.7 避免名字中出現數字編號:盡量避免名字中出現數字編號,如Value1,Value2
等,除非邏輯上的確需要編號。這是為了防止程序員偷懶,不肯為命名動腦筋而導致產生無意義的名字(因為用數字編號最省事)。
T,C,M,R類
2.1 T類表示簡單數據類型,不對資源擁有控制權,在析構過程中沒有釋放資源動作。
2.2 C表示從CBase繼承的類。該類不能從棧上定義變量,只能從堆上創建。
2.3 M表示接口類。
2.4 R是資源類,通常是系統固有類型。除了特殊情況,不應在開發代碼中出現R類型。
函數名
3.1 M類的函數名稱應采用HandleXXX
命名,例如:HandleTimerEvent
;不推薦采用java風格,例如 handleTimerEvent
;除了標准c風格代碼,不推薦用下劃線,例如,handle_event
。
3.2 Leave函數,用後綴L。
3.3 Leave函數,且進清除棧,用後綴LC。
3.4 Leave函數,且刪除對象,用後綴LD。
函數參數
4.1 函數參數用a作為前綴。
4.2 避免出現和匈牙利混合的命名規則如apBuffer
名稱。用aBuffer
即可。
4.3 函數參數比較多時,應考慮用結構代替。
4.4 如果不能避免函數參數比較多,應在排版上可考慮每個參數占用一行,參數名豎向對齊。
成員變量
5.1 成員變量用m最為前綴。
5.2 避免出現和匈牙利混合的命名規則如mpBuffer
名稱。用mBuffer
即可。
局部變量
6.1 循環變量和簡單變量采用簡單小寫字符串即可。例如,int i
;
6.2 指針變量用p
打頭,例如void* pBuffer;
全局變量
7.1 全局變量用g_
最為前綴。
類名
8.1 類和對象名應是名詞。
8.2 實現行為的類成員函數名應是動詞。
8.3 類的存取和查詢成員函數名應是名詞或形容詞。
風格兼容性
9.1 對於移植的或者開源的代碼,可以沿用原有風格,不用C++的命名規范。
四、代碼風格方面
Tab和空格
1.1 每一行開始處的縮進只能用Tab,不能用空格,輸入內容之後統一用空格。除了最開始的縮進控制用Tab,其他部分為了對齊,需要使用空格進行縮進。這樣可以避免在不同的編輯器下顯示不對齊的情況。
1.2 在代碼行的結尾部分不能出現多余的空格。
1.3 不要在"::","->","."
前後加空格。
1.4 不要在",",";"
之前加空格。
類型定義和{
2.1 類,結構,枚舉,聯合:大括號另起一行
函數
3.1 函數體的{需要新起一行,在{之前不能有縮進。
3.2 除了特別情況,函數體內不能出現兩個空行。
3.3 除了特別情況,函數體內不能宏定義指令。
3.4 在一個函數體內,邏揖上密切相關的語句之間不加空行,其它地方應加空行分隔。
3.5 在頭文件定義的inline
函數,函數之間可以不用空行,推薦用一個空行。
代碼塊
4.1 "if"、"for"、"while"、"do"、"try"、"catch"
等語句自占一行,執行語句不得緊跟其後。不論執行語句有多少都要加 "{ }"
。這樣可以防止書寫和修改代碼時出現失誤。
4.2 "if"、"for"、"while"、"do"、"try"、"catch"
的括號和表達式,括號可緊挨關鍵字,這樣強調的是表達式。
else
5.1 if語句如果有else語句,用 } else { 編寫為一行,不推薦用 3 行代碼的方式。
代碼行
6.1 一行代碼只做一件事情,如只定義一個變量,或只寫一條語句。這樣的代碼容易閱讀,並且方便於寫注釋。
6.2 多行變量定義,為了追求代碼排版美觀,可將變量豎向對齊。
6.3 代碼行最大長度宜控制在一定個字符以內,能在當前屏幕內全部可見為宜。
switch語句
7.1 case關鍵字應和switch對齊。
7.2 case子語句如果有變量,應用{}包含起來。
7.3 如果有並列的類似的簡單case語句,可考慮將case代碼塊寫為一行代碼。
7.4 簡單的case之間可不用空行,復雜的case之間應考慮用空行分割開。
7.5 case字語句的大括號另起一行,不要和case寫到一行。
7.6 為所有switch語句提供default分支。
7.7 若某個case不需要break一定要加注釋聲明。
循環
8.1 空循環可用 for( ;; )
或者 while( 1 )
或者 while( true )
類
9.1 類繼承應采用每個基類占據一行的方式。
9.2 單繼承可將基類放在類定義的同一行。如果用多行,則應用Tab縮進。
9.3 多繼承在基類比較多的情況下,應將基類分行,並采用Tab縮進對齊。
9.4 重載基類虛函數,應在該組虛函數前寫注釋 // implement XXX
9.5 友元聲明放到類的末尾。
宏
10.1 不要用分號結束宏定義。
10.2 函數宏的每個參數都要括起來。
10.3 不帶參數的宏函數也要定義成函數形式。
goto
11.1 盡量不要用goto。
五、類型
定義指針和引用時*
和&
緊跟類型。 盡量避免使用浮點數,除非必須。 用typedef
簡化程序中的復雜語法。 避免定義無名稱的類型。例如:typedef enum { EIdle, EActive } TState;
少用union
,如果一定要用,則采用簡單數據類型成員。 用enum
取代(一組相關的)常量。 不要使用魔鬼數字。 盡量用引用取代指針。 定義變量完成後立即初始化,勿等到使用時才進行。 如果有更優雅的解決方案,不要使用強制類型轉換。
六、表達式
避免在表達式中用賦值語句。 避免對浮點類型做等於或不等於判斷。 不能將枚舉類型進行運算後再賦給枚舉變量。 在循環過程中不要修改循環計數器。 檢測空指針,用if( p )
檢測非空指針,用 if( ! p )
七、函數
引用
1.1 引用類型作為返回值:函數必須返回一個存在的對象。
1.2 引用類型作為參數:調用者必須傳遞一個存在的對象。
常量成員函數
2.1 表示該函數只讀取對象的內容,不會對對象進行修改。
返回值
3.1 除開void
函數,構造函數,析構函數,其它函數必須要有返回值。
3.2 當函數返回引用或指針時,用文字描述其有效期。
內聯函數
4.1 內聯函數應將函數體放到類體外。
4.2 只有簡單的函數才有必要設計為內聯函數,復雜業務邏輯的函數不要這麼做。
4.3 虛函數不要設計為內聯函數。
函數參數
5.1 只讀取該參數的內容,不對其內容做修改,用常量引用。
5.2 修改參數內容,或需要通過參數返回,用非常量應用。
5.3 簡單數據類型用傳值方式。
5.4 復雜數據類型用引用或指針方式。
八、類
構造函數
1.1 構造函數的初始化列表,應和類的順序一致。
1.2 初始化列表中的每個項,應獨占一行。
1.3 避免出現用一個成員初始化另一個成員。
1.4 構造函數應初始化所有成員,尤其是指針。
1.5 不要在構造函數和析構函數中拋出異常。
純虛函數
2.1 M類的虛函數應設計為純虛函數。
構造和析構函數
3.1 如果類可以繼承,則應將類析構函數設計為虛函數。
3.2 如果類不允許繼承,則應將類析構函數設計為非虛函數。
3.3 如果類不能被復制,則應將拷貝構造函數和賦值運算符設計為私有的。
3.4 如果為類設計了構造函數,則應有析構函數。
成員變量
4.1 盡量避免使用mutable
和volatile
。
4.2 盡量避免使用公有成員變量。
成員函數
5.1 努力使類的接口少而完備。
5.2 盡量使用常成員函數代替非常成員函數,const
函數
5.3 除非特別理由,絕不要重新定義(繼承來的)非虛函數。(這樣是覆蓋,基類的某些屬性無初始化)
繼承
6.1 繼承必須滿足IS-A的關系,HAS-A應采用包含。
6.2 虛函數不要采用默認參數。
6.3 除非特別需要,應避免設計大而全的虛函數,虛函數功能要單一。
6.4 除非特別需要,避免將基類強制轉換成派生類。
友元
7.1 盡量避免使用友元函數和友元類。
九、錯誤處理
申請內存用new
操作符。 釋放內存用delete
操作符。 new
和delete
,new[]
和delete[]
成對使用。 申請內存完成之後,要檢測指針是否申請成功,處理申請失敗的情況。 誰申請誰釋放。優先級:函數層面,類層面,模塊層面。 釋放內存完成後將指針賦空,避免出現野指針。 使用指針前進行判斷合法性,應考慮到為空的情況的處理。 使用數組時,應先判斷索引的有效性,處理無效的索引的情況。 代碼不能出現編譯警告。 使用錯誤傳遞的錯誤處理思想。 衛句風格:先處理所有可能發生錯誤的情況,再處理正常情況。 嵌套do-while(0)
宏:目的是將一組語句變成一個語句,避免被其他if等中斷。
十、性能
使用前向聲明代替#include
指令。Class M;
盡量用++i
代替i++
。即用前綴代替後綴運算。 盡量在for
循環之前,先寫計算估值表達式。 盡量避免在循環體內部定義對象。 避免對象拷貝,尤其是代價很高的對象拷貝。 避免生成臨時對象,尤其是大的臨時對象。 注意大尺寸對象數組。 80-20原則。
十一、兼容性
遵守ANSI C和ISO C++國際標准。 確保類型轉換不會丟失信息。 注意雙字節字符的兼容性。 注意運算溢出問題。 不要假設類型的存儲尺寸。 不要假設表達式的運算順序。 不要假設函數參數的計算順序。 不要假設不同源文件中靜態或全局變量的初始化順序。 不要依賴編譯器基於實現、未明確或未定義的功能。 將所有#include
的文件名視為大小寫敏感。 避免使用全局變量、靜態變量、函數靜態變量、類靜態變量。在使用靜態庫,動態庫,多線程環境時,會導致兼容性問題。 不要重新實現標准庫函數,如STL已經存在的。