華爲C語言編程規範(精華總結)

目錄

1、代碼總體原則

2、頭文件

2、函數

3、標識符命名與定義

4、變量

5、宏、常量

6、表達式

7、註釋

8、排版與格式

9、代碼編輯編譯 


“編寫程序應該以人爲本,計算機第二。”                                                               

                                                                                                                                       ——Steve McConnell

“無緣進華爲,但可以用華爲的標準要求自己。”                                                               

                                                                                                                                       ——不脫髮的程序猿


1、代碼總體原則

1、清晰第一 

清晰性是易於維護、易於重構的程序必需具備的特徵。代碼首先是給人讀的,好的代碼應當可以像文章一樣發聲朗誦出來。

目前軟件維護期成本佔整個生命週期成本的40%~90%。根據業界經驗,維護期變更代碼的成本,小型系統是開發期的5倍,大型系統(100萬行代碼以上)可以達到100倍。業界的調查指出,開發組平均大約一半的人力用於彌補過去的錯誤,而不是添加新的功能來幫助公司提高競爭力。

一般情況下,代碼的可閱讀性高於性能,只有確定性能是瓶頸時,才應該主動優化。

2、簡潔爲美

簡潔就是易於理解並且易於實現。代碼越長越難以看懂,也就越容易在修改時引入錯誤。寫的代碼越多,意味着出錯的地方越多,也就意味着代碼的可靠性越低。因此,我們提倡大家通過編寫簡潔明瞭的代碼來提升代碼可靠性。

廢棄的代碼(沒有被調用的函數和全局變量)要及時清除,重複代碼應該儘可能提煉成函數。

3、選擇合適的風格,與代碼原有風格保持一致

產品所有人共同分享同一種風格所帶來的好處,遠遠超出爲了統一而付出的代價。在公司已有編碼規範的指導下,審慎地編排代碼以使代碼儘可能清晰,是一項非常重要的技能。 如果重構/ / 修改其他風格的代碼時,比較明智的做法是根據 現有 代碼 的 現有風格繼續編寫代碼,或者使用格式轉換工具進行轉換成公司內部風格。


2、頭文件

對於C語言來說,頭文件的設計體現了大部分的系統設計。 不合理的頭文件佈局是編譯時間過長的根因,不合理的頭文件實際上反映了不合理的設計。

1、頭文件中適合放置接口的聲明,不適合放置實現

頭文件是模塊(Module)或單元(Unit)的對外接口。頭文件中應放置對外部的聲明,如對外提供的函數聲明、宏定義、類型定義等。

要求:

  • 內部使用的函數(相當於類的私有方法)聲明不應放在頭文件中。
  • 內部使用的宏、枚舉、結構定義不應放入頭文件中。
  • 變量定義不應放在頭文件中,應放在.c文件中。
  • 變量的聲明儘量不要放在頭文件中,亦即儘量不要使用全局變量作爲接口。變量是模塊或單元的內部實現細節,不應通過在頭文件中聲明的方式直接暴露給外部,應通過函數接口的方式進行對外暴露。 即使必須使用全局變量,也只應當在.c中定義全局變量,在.h中僅聲明變量爲全局的。

2、頭文件應當職責單一,切忌依賴複雜

頭文件過於複雜,依賴過於複雜是導致編譯時間過長的主要原因。很多現有代碼中頭文件過大,職責過多,再加上循環依賴的問題,可能導致爲了在.c中使用一個宏,而包含十幾個頭文件。

錯誤示例:某平臺定義WORD類型的頭文件:

#include <VXWORKS.H>
#include <KERNELLIB.H>
#include <SEMLIB.H>
#include <INTLIB.H>
#include <TASKLIB.H>
#include <MSGQLIB.H>
#include <STDARG.H>
#include <FIOLIB.H>
#include <STDIO.H>
#include <STDLIB.H>
#include <CTYPE.H>
#include <STRING.H>
#include <ERRNOLIB.H>
#include <TIMERS.H>
#include <MEMLIB.H>
#include <TIME.H>
#include <WDLIB.H>
#include <SYSLIB.H>
#include <TASKHOOKLIB.H>
#include <REBOOTLIB.H>
…
typedef unsigned short WORD;
…

這個頭文件不但定義了基本數據類型WORD,還包含了stdio.h syslib.h等等不常用的頭文件。如果工程中有10000個源文件,而其中100個源文件使用了stdio.h的printf,由於上述頭文件的職責過於龐大,而WORD又是每一個文件必須包含的,從而導致stdio.h/syslib.h等可能被不必要的展開了9900次,大大增加了工程的編譯時間。

3、頭文件應向穩定的方向包含 

頭文件的包含關係是一種依賴,一般來說,應當讓不穩定的模塊依賴穩定的模塊,從而當不穩定的模塊發生變化時,不會影響(編譯)穩定的模塊。

就我們的產品來說,依賴的方向應該是: 產品依賴於平臺,平臺依賴於標準庫。某產品線平臺的代碼中已經包含了產品的頭文件,導致平臺無法單獨編譯、發佈和測試,是一個非常糟糕的反例。除了不穩定的模塊依賴於穩定的模塊外,更好的方式是兩個模塊共同依賴於接口,這樣任何一個模塊的內部實現更改都不需要重新編譯另外一個模塊。在這裏,我們假設接口本身是最穩定的。

4、每一個 .c 文件應有一個同名 .h 文件,用於聲明需要對外公開的接口

如果一個.c文件不需要對外公佈任何接口,則其就不應當存在,除非它是程序的入口,如main函數所在的文件。

現有某些產品中,習慣一個.c文件對應兩個頭文件,一個用於存放對外公開的接口,一個用於存放內部需要用到的定義、聲明等,以控制.c文件的代碼行數。編者不提倡這種風格。這種風格的根源在於源文件過大,應首先考慮拆分.c文件,使之不至於太大。另外,一旦把私有定義、聲明放到獨立的頭文件中,就無法從技術上避免別人include之,難以保證這些定義最後真的只是私有的。

5、禁止頭文件循環依賴

頭文件循環依賴,指a.h包含b.h,b.h包含c.h,c.h包含a.h之類導致任何一個頭文件修改,都導致所有包含了a.h/b.h/c.h的代碼全部重新編譯一遍。而如果是單向依賴,如a.h包含b.h,b.h包含c.h,而c.h不包含任何頭文件,則修改a.h不會導致包含了b.h/c.h的源代碼重新編譯。

6、  .c/.h文件禁止包含用不到的頭文件

很多系統中頭文件包含關係複雜,開發人員爲了省事起見,可能不會去一一鑽研,直接包含一切想到的頭文件,甚至有些產品乾脆發佈了一個god.h,其中包含了所有頭文件,然後發佈給各個項目組使用,這種只圖一時省事的做法,導致整個系統的編譯時間進一步惡化,並對後來人的維護造成了巨大的麻煩。

7、  頭文件應當自包含 

簡單的說,自包含就是任意一個頭文件均可獨立編譯。如果一個文件包含某個頭文件,還要包含另外一個頭文件才能工作的話,就會增加交流障礙,給這個頭文件的用戶增添不必要的負擔。

示例:如果a.h不是自包含的,需要包含b.h才能編譯,會帶來的危害:每個使用a.h頭文件的.c文件,爲了讓引入的a.h的內容編譯通過,都要包含額外的頭文件b.h。額外的頭文件b.h必須在a.h之前進行包含,這在包含順序上產生了依賴。

注意:該規則需要與“.c/.h文件禁止包含用不到的頭文件”規則一起使用,不能爲了讓a.h自包含,而在a.h中包含不必要的頭文件。a.h要剛剛可以自包含,不能在a.h中多包含任何滿足自包含之外的其他頭文件。

8、總是編寫內部 #include 保護符( #define  保護)

多次包含一個頭文件可以通過認真的設計來避免。如果不能做到這一點,就需要採取阻止頭文件內容被包含多於一次的機制。通常的手段是爲每個文件配置一個宏,當頭文件第一次被包含時就定義這個宏,並在頭文件被再次包含時使用它以排除文件內容。所有頭文件都應當使用#define 防止頭文件被多重包含,命名格式爲FILENAME_H,爲了保證唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。

注:沒有在宏最前面加上單下劃線"_",是因爲一般以單下劃線"_"和雙下劃線"__"開頭的標識符爲ANSIC等使用,在有些靜態檢查工具中,若全局可見的標識符以"_"開頭會給出告警。

定義包含保護符時,應該遵守如下規則:

  • 保護符使用唯一名稱;

  • 不要在受保護部分的前後放置代碼或者註釋。

正確示例:假定VOS工程的timer模塊的timer.h,其目錄爲VOS/include/timer/timer.h,應按如下方式保護:

#ifndef VOS_INCLUDE_TIMER_TIMER_H
#define VOS_INCLUDE_TIMER_TIMER_H
...
#endif

也可以使用如下簡單方式保護:

#ifndef TIMER_H
#define TIMER_H
...
#endif

例外情況:頭文件的版權聲明部分以及頭文件的整體註釋部分(如闡述此頭文件的開發背景、使用注意事項等)可以放在保護符(#ifndef XX_H)前面。

9、禁止在頭文件中定義變量

在頭文件中定義變量,將會由於頭文件被其他.c文件包含而導致變量重複定義。

10、只能通過包含頭文件的方式使用其他 .c 提供的接口,禁止在.c 中通過 extern 的方式使用外部函數接口、變量

若a.c使用了b.c定義的foo()函數,則應當在b.h中聲明extern int foo(int input);並在a.c中通過#include <b.h>來使用foo。禁止通過在a.c中直接寫extern int foo(int input);來使用foo,後面這種寫法容易在foo改變時可能導致聲明和定義不一致。

11、禁止在 extern "C" 中包含頭文件

在extern "C"中包含頭文件,會導致extern "C"嵌套,Visual Studio對extern "C"嵌套層次有限制,嵌套層次太多會編譯錯誤。
在extern "C"中包含頭文件,可能會導致被包含頭文件的原有意圖遭到破壞。

錯誤示例:

extern “C”
{
#include “xxx.h”
...
}

正確示例:

#include “xxx.h”
extern “C”
{
...
} 

12、一個模塊通常包含多個 .c 文件,建議放在同一個目錄下,目錄名即爲模塊名。爲方便外部使用者,建議每一個模塊提供一個 .h ,文件名爲目錄名

需要注意的是,這個.h並不是簡單的包含所有內部的.h,它是爲了模塊使用者的方便,對外整體提供的模塊接口。以Google test(簡稱GTest)爲例,GTest作爲一個整體對外提供C++單元測試框架,其1.5版本的gtest工程下有6個源文件和12個頭文件。但是它對外只提供一個gtest.h,只要包含gtest.h即可使用GTest提供的所有對外提供的功能,使用者不必關係GTest內部各個文件的關係,即使以後GTest的內部實現改變了,比如把一個源文件c拆成兩個源文件,使用者也不必關心,甚至如果對外功能不變,連重新編譯都不需要。對於有些模塊,其內部功能相對鬆散,可能並不一定需要提供這個.h,而是直接提供各個子模塊或者.c的頭文件。

比如產品普遍使用的VOS,作爲一個大模塊,其內部有很多子模塊,他們之間的關係相對比較鬆散,就不適合提供一個vos.h。而VOS的子模塊,如Memory(僅作舉例說明,與實際情況可能有所出入),其內部實現高度內聚,雖然其內部實現可能有多個.c和.h,但是對外只需要提供一個Memory.h聲明接口。

13、如果一個模塊包含多個子模塊,則建議每一個子模塊提供一個對外的 .h,文件名爲子模塊名

降低接口使用者的編寫難度

14、頭文件不要使用非習慣用法的擴展名,如 .inc 

目前很多產品中使用了.inc作爲頭文件擴展名,這不符合c語言的習慣用法。在使用.inc作爲頭文件擴展名的產品,習慣上用於標識此頭文件爲私有頭文件。但是從產品的實際代碼來看,這一條並沒有被遵守,一個.inc文件被多個.c包含比比皆是。

除此之外,使用.inc還導致source insight、Visual stduio等IDE工具無法識別其爲頭文件,導致很多功能不可用,如“跳轉到變量定義處”。雖然可以通過配置,強迫IDE識別.inc爲頭文件,但是有些軟件無法配置,如Visual Assist只能識別.h而無法通過配置識別.inc。

15、同一產品統一包含頭文件排列方式

常見的包含頭文件排列方式:功能塊排序、文件名升序、穩定度排序。

正確示例1:以升序方式排列頭文件可以避免頭文件被重複包含:

#include <a.h>
#include <b.h>
#include <c/d.h>
#include <c/e.h>
#include <f.h>

正確示例2:以穩定度排序,建議將不穩定的頭文件放在前面,如把產品的頭文件放在平臺的頭文件前面:

#include <product.h>
#include <platform.h>

相對來說,product.h修改的較爲頻繁,如果有錯誤,不必編譯platform.h就可以發現product.h的錯誤,可以部分減少編譯時間。


2、函數

函數設計的精髓:編寫整潔函數,同時把代碼有效組織起來。

整潔函數要求:代碼簡單直接、不隱藏設計者的意圖、用乾淨利落的抽象和直截了當的控制語句將函數有機組織起來。

代碼的有效組織包括:邏輯層組織和物理層組織兩個方面。邏輯層,主要是把不同功能的函數通過某種聯繫組織起來,主要關注模塊間的接口,也就是模塊的架構。物理層,無論使用什麼樣的目錄或者名字空間等,需要把函數用一種標準的方法組織起來。例如:設計良好的目錄結構、函數名字、文件組織等,這樣可以方便查找。

1、一個函數僅完成一件功能

一個函數實現多個功能給開發、使用、維護都帶來很大的困難。

將沒有關聯或者關聯很弱的語句放到同一函數中,會導致函數職責不明確,難以理解,難以測試和改動。

2、重複代碼應該儘可能提煉成函數

重複代碼提煉成函數可以帶來維護成本的降低。

重複代碼是我司不良代碼最典型的特徵之一。在“代碼能用就不改”的指導原則之下,大量的煙囪式設計及其實現充斥着各產品代碼之中。新需求增加帶來的代碼拷貝和修改,隨着時間的遷移,產品中堆砌着許多類似或者重複的代碼。

項目組應當使用代碼重複度檢查工具,在持續集成環境中持續檢查代碼重複度指標變化趨勢,並對新增重複代碼及時重構。當一段代碼重複兩次時,即應考慮消除重複,當代碼重複超過三次時,應當立刻着手消除重複。

3、避免函數過長,新增函數不超過 50 行 (非空非註釋行) 

過長的函數往往意味着函數功能不單一,過於複雜。

函數的有效代碼行數,即NBNC(非空非註釋行)應當在[1,50]區間。

例外:某些實現算法的函數,由於算法的聚合性與功能的全面性,可能會超過50行。

延伸閱讀材料: 業界普遍認爲一個函數的代碼行不要超過一個屏幕,避免來回翻頁影響閱讀;一般的代碼度量工具建議都對此進行檢查,例如Logiscope的函數度量:"Number of Statement" (函數中的可執行語句數)建議不超過20行,QA C建議一個函數中的所有行數(包括註釋和空白行)不超過50行。

4、避免函數的代碼塊嵌套過深,新增函數的代碼塊嵌套不超過4層

函數的代碼塊嵌套深度指的是函數中的代碼控制塊(例如:if、for、while、switch等)之間互相包含的深度。每級嵌套都會增加閱讀代碼時的腦力消耗,因爲需要在腦子裏維護一個“棧”(比如,進入條件語句、進入循環„„)。應該做進一步的功能分解,從而避免使代碼的閱讀者一次記住太多的上下文。優秀代碼參考值:[1, 4]。

錯誤示例:代碼嵌套深度爲5層:

void serial (void)
{
    if (!Received)
    {
        TmoCount = 0;
         switch (Buff)
        {
            case AISGFLG:
                if ((TiBuff.Count > 3)&& ((TiBuff.Buff[0] == 0xff) || (TiBuf.Buff[0] == CurPa.ADDR)))
                {
                    Flg7E = false;
                    Received = true;
                }
                else
                {
                    TiBuff.Count = 0;
                    Flg7D = false;
                    Flg7E = true;
                }
                break;
            default:
                break;
        }
    }
}

5、 可重入函數應避免使用共享變量;若需要使用,則應通過互斥手段(關中斷、信號量)對其加以保護

可重入函數是指可能被多個任務併發調用的函數。在多任務操作系統中,函數具有可重入性是多個任務可以共用此函數的必要條件。共享變量指的全局變量和static變量。編寫C語言的可重入函數時,不應使用static局部變量,否則必須經過特殊處理,才能使函數具有可重入性。

示例:函數square_exam返回g_exam平方值。那麼如下函數不具有可重入性。

int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    g_exam = para; // (**)
    temp = square_exam ( );
    return temp;
}

此函數若被多個線程調用的話,其結果可能是未知的,因爲當(**)語句剛執行完後,另外一個使用本函數的線程可能正好被激活,那麼當新激活的線程執行到此函數時,將使g_exam賦於另一個不同的para值,所以當控制重新回到“temp =square_exam ( )”後,計算出的temp很可能不是預想中的結果。此函數應如下改進。 

int g_exam;
unsigned int example( int para )
{
    unsigned int temp;
    [申請信號量操作] // 若申請不到“信號量”,說明另外的進程正處於
    g_exam = para; //給g_exam賦值並計算其平方過程中(即正在使用此
    temp = square_exam( ); // 信號),本進程必須等待其釋放信號後,纔可繼
    [釋放信號量操作] // 續執行。其它線程必須等待本線程釋放信號量後
    // 才能再使用本信號。
    return temp;
}

6、對參數的合法性檢查,由調用者負責還是由接口函數負責,應在項目組/模塊內應統一規定。缺省由調用者負責。

對於模塊間接口函數的參數的合法性檢查這一問題,往往有兩個極端現象,即:要麼是調用者和被調用者對參數均不作合法性檢查,結果就遺漏了合法性檢查這一必要的處理過程,造成問題隱患;要麼就是調用者和被調用者均對參數進行合法性檢查,這種情況雖不會造成問題,但產生了冗餘代碼,降低了效率。

7、對函數的錯誤返回碼要全面處理

一個函數(標準庫中的函數/第三方庫函數/用戶定義的函數)能夠提供一些指示錯誤發生的方法。這可以通過使用錯誤標記、特殊的返回數據或者其他手段,不管什麼時候函數提供了這樣的機制,調用程序應該在函數返回時立刻檢查錯誤指示。

8、設計高扇入,合理扇出(小於7)的函數

扇出是指一個函數直接調用(控制)其它函數的數目,而扇入是指有多少上級函數調用它。

扇出過大,表明函數過分複雜,需要控制和協調過多的下級函數;而扇出過小,例如:總是1,表明函數的調用層次可能過多,這樣不利於程序閱讀和函數結構的分析,並且程序運行時會對系統資源如堆棧空間等造成壓力。通常函數比較合理的扇出(調度函數除外)通常是3~5。 

扇出太大,一般是由於缺乏中間層次,可適當增加中間層次的函數。扇出太小,可把下級函數進一步分解多個函數,或合併到上級函數中。當然分解或合併函數時,不能改變要實現的功能,也不能違背函數間的獨立性。扇入越大,表明使用此函數的上級函數越多,這樣的函數使用效率高,但不能違背函數間的獨立性而單純地追求高扇入。公共模塊中的函數及底層函數應該有較高的扇入。

較良好的軟件結構通常是頂層函數的扇出較高,中層函數的扇出較少,而底層函數則扇入到公共模塊中。

9、廢棄代碼(沒有被調用的函數和變量) ) 要及時清除

程序中的廢棄代碼不僅佔用額外的空間,而且還常常影響程序的功能與性能,很可能給程序的測試、維護等造成不必要的麻煩。

10、函數不變參數使用const 

不變的值更易於理解/跟蹤和分析,把const作爲默認選項,在編譯時會對其進行檢查,使代碼更牢固/更安全。

正確示例:C99標準 7.21.4.4 中strncmp 的例子,不變參數聲明爲const。

int strncmp(const char *s1, const char *s2, register size_t n)
{
    register unsigned char u1, u2;
    while (n-- > 0)
    {
        u1 = (unsigned char) *s1++;
        u2 = (unsigned char) *s2++;
        if (u1 != u2)
        {
            return u1 - u2;
        }
        if (u1 == '\0')
        {
            return 0;
        }
    }
    return 0;
}

11、函數應避免使用全局變量、靜態局部變量和 I/O 操作,不可避免的地方應集中使用

帶有內部“存儲器”的函數的功能可能是不可預測的,因爲它的輸出可能取決於內部存儲器(如某標記)的狀態。這樣的函數既不易於理解又不利於測試和維護。在C語言中,函數的static局部變量是函數的內部存儲器,有可能使函數的功能不可預測。

錯誤示例:如下函數,其返回值(即功能)是不可預測的。

unsigned int integer_sum( unsigned int base )
{
    unsigned int index;
    static unsigned int sum = 0;// 注意,是static類型的。
    // 若改爲auto類型,則函數即變爲可預測。
    for (index = 1; index <= base; index++)
    {
        sum += index;
    }
    return sum;
}

12、檢查函數所有非參數輸入的有效性,如數據文件、公共變量等

函數的輸入主要有兩種:一種是參數輸入;另一種是全局變量、數據文件的輸入,即非參數輸入。函數在使用輸入參數之前,應進行有效性檢查。

13、 函數的參數個數不超過5個

函數的參數過多,會使得該函數易於受外部(其他部分的代碼)變化的影響,從而影響維護工作。函數的參數過多同時也會增大測試的工作量。

函數的參數個數不要超過5個,如果超過了建議拆分爲不同函數。

14、除打印類函數外,不要使用可變長參函數。

可變長參函數的處理過程比較複雜容易引入錯誤,而且性能也比較低,使用過多的可變長參函數將導致函數的維護難度大大增加。

15、在源文件範圍內聲明和定義的所有函數,除非外部可見,否則應該增加static關鍵字

如果一個函數只是在同一文件中的其他地方調用,那麼就用static聲明。使用static確保只是在聲明它的文件中是可見的,並且避免了和其他文件或庫中的相同標識符發生混淆的可能性。

正確示例:建議定義一個STATIC宏,在調試階段,將STATIC定義爲static,版本發佈時,改爲空,以便於後續的打熱補丁等操作。

#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif

3、標識符命名與定義

標識符的命名規則歷來是一個敏感話題,典型的命名風格如unix風格、windows風格等,從來無法達成共識。實際上,各種風格都有其優勢也有其劣勢,而且往往和個人的審美觀有關。我們對標識符定義主要是爲了讓團隊的代碼看起來儘可能統一,有利於代碼的後續閱讀和修改,產品可以根據自己的實際需要指定命名風格,規範中不再做統一的規定。

1、標識符的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解

儘可能給出描述性名稱,不要節約空間,讓別人很快理解你的代碼更重要。

正確示例:

int error_number;
int number_of_completed_connection;

錯誤示例:

int n;
int nerr;
int n_comp_conns;

2、除了常見的通用縮寫以外,不使用單詞縮寫,不得使用漢語拼音

較短的單詞可通過去掉“元音”形成縮寫,較長的單詞可取單詞的頭幾個字母形成縮寫,一些單詞有大家公認的縮寫,常用單詞的縮寫必須統一。協議中的單詞的縮寫與協議保持一致。對於某個系統使用的專用縮寫應該在注視或者某處做統一說明。

正確示例:一些常見可以縮寫的例子:

argument 可縮寫爲 arg
buffer 可縮寫爲 buff
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 可縮寫爲 para
previous 可縮寫爲 prev
register 可縮寫爲 reg
semaphore 可縮寫爲 sem
statistic 可縮寫爲 stat
synchronize 可縮寫爲 sync
temp 可縮寫爲 tmp

3、產品/項目組內部應保持統一的命名風格

Unix like和windows like風格均有其擁躉,產品應根據自己的部署平臺,選擇其中一種,並在產品內部保持一致。

4、用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等

正確示例:

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/paste up/down

5、儘量避免名字中出現數字編號,除非邏輯上的確需要編號

錯誤示例:如下命名,使人產生疑惑。

#define EXAMPLE_0_TEST_
#define EXAMPLE_1_TEST_

正確示例:應改爲有意義的單詞命名。

#define EXAMPLE_UNIT_TEST_
#define EXAMPLE_ASSERT_TEST_

6、標識符前不應添加模塊、項目、產品、部門的名稱作爲前綴

很多已有代碼中已經習慣在文件名中增加模塊名,這種寫法類似匈牙利命名法,導致文件名不可讀,並且帶來帶來如下問題:

  • 第一眼看到的是模塊名,而不是真正的文件功能,阻礙閱讀;
  • 文件名太長;
  • 文件名和模塊綁定,不利於維護和移植。若foo.c進行重構後,從a模塊挪到b模塊,若foo.c
  • 中有模塊名,則需要將文件名從a_module_foo.c改爲b_module_foo.c。

7、平臺/ / 驅動等適配代碼的標識符命名風格保持和平臺

涉及到外購芯片以及配套的驅動,這部分的代碼變動(包括爲產品做適配的新增代碼),應該保持原有的風格。

8、重構/修改部分代碼時,應保持和原有代碼的命名風格一致

根據源代碼現有的風格繼續編寫代碼,有利於保持總體一致。

9、文件命名統一採用小寫字符

因爲不同系統對文件名大小寫處理會不同(如MS的DOS、Windows系統不區分大小寫,但是Linux系統則區分),所以代碼文件命名建議統一採用全小寫字母命名。

10、全局變量應增加“g_” 前綴,靜態變量應增加“s_”

首先,全局變量十分危險,通過前綴使得全局變量更加醒目,促使開發人員對這些變量的使用更加小心。

其次,從根本上說,應當儘量不使用全局變量,增加g_和s_前綴,會使得全局變量的名字顯得很醜陋,從而促使開發人員儘量少使用全局變量。

11、禁止使用單字節命名變量,但 允許 定義i 、j、k作爲局部循環變量

12、 不建議使用匈牙利命名法

匈牙利命名法是一種編程時的命名規範。基本原則是:變量名=屬性+類型+對象描述。匈牙利命名法源於微軟,然而卻被很多人以訛傳訛的使用。而現在即使是微軟也不再推薦使用匈牙利命名法。歷來對匈牙利命名法的一大詬病,就是導致了變量名難以閱讀,這和本規範的指導思想也有衝突,所以本規範特意強調,變量命名不應採用匈牙利命名法,而應該想法使變量名爲一個有意義的詞或詞組,方便代碼的閱讀。

變量命名需要說明的是變量的含義,而不是變量的類型。在變量命名前增加類型說明,反而降低了變量的可讀性;更麻煩的問題是,如果修改了變量的類型定義,那麼所有使用該變量的地方都需要修改。

13、使用名詞或者形容詞+名詞方式命名變量 

14、函數命名應以函數要執行的動作命名,一般採用動詞或者動詞+名詞的結構

正確示例:找到當前進程的當前目錄:

DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );

15、函數指針除了前綴,其他按照函數的命名規則命名

16、對於數值或者字符串等等常量的定義,建議採用全大寫字母,單詞之間加下劃線“_”的方式命名(枚舉同樣建議使用此方式定義)

正確示例:

#define PI_ROUNDED 3.14

17、除了頭文件或編譯開關等特殊標識定義,宏定義不能使用下劃線“_”開頭和結尾

一般來說,‟_‟開頭、結尾的宏都是一些內部的定義,ISO/IEC 9899(俗稱C99)中有如下的描述(6.10.8 Predefined macro names):

None of these macro names (這裏上面是一些內部定義的宏的描述),nor the identifier defined,shall be the subject of a #define or a #undef preprocessing directive.Any other predefined macro names shall begin with a leading underscore fol lowedby an uppercase letter ora second underscore.


4、變量

1、一個變量只有一個功能,不能把一個變量用作多種用途

一個變量只用來表示一個特定功能,不能把一個變量作多種用途,即同一變量取值不同時,其代表的意義也不同。

錯誤示例:具有兩種功能的反例

WORD DelRelTimeQue( void )
{
    WORD Locate;
    Locate = 3; 
    Locate = DeleteFromQue(Locate); /* Locate具有兩種功能:位置和函數DeleteFromQue的返回值 */
    return Locate;
}

正確做法:使用兩個變量

WORD DelRelTimeQue( void )
{
    WORD Ret;
    WORD Locate;
    Locate = 3;
    Ret  = DeleteFromQue(Locate);
    return Ret;
}

2、結構功能單一,不要設計面面俱到的數據結構 

相關的一組信息纔是構成一個結構體的基礎,結構的定義應該可以明確的描述一個對象,而不是一組相關性不強的數據的集合。設計結構時應力爭使結構代表一種現實事務的抽象,而不是同時代表多種。結構中的各元素應代表同一事務的不同側面,而不應把描述沒有關係或關係很弱的不同事務的元素放到同一結構中。

錯誤示例:如下結構不太清晰、合理。

typedef struct STUDENT_STRU
{
    unsigned char name[32]; /* student's name */
    unsigned char age; /* student's age */
    unsigned char sex; /* student's sex, as follows */
    /* 0 - FEMALE; 1 - MALE */
    unsigned char teacher_name[32]; /* the student teacher's name */
    unsigned char teacher_sex; /* his teacher sex */
} STUDENT;

正確示例:若改爲如下,會更合理些。

typedef struct TEACHER_STRU
{
    unsigned char name[32]; /* teacher name */
    unsigned char sex; /* teacher sex, as follows */
    /* 0 - FEMALE; 1 - MALE */
    unsigned int teacher_ind; /* teacher index */
} TEACHER;

typedef struct STUDENT_STRU
{
    unsigned char name[32]; /* student's name */
    unsigned char age; /* student's age */
    unsigned char sex; /* student's sex, as follows */
    /* 0 - FEMALE; 1 - MALE */
    unsigned int teacher_ind; /* his teacher index */
} STUDENT; 

3、不用或者少用全局變量

單個文件內部可以使用static的全局變量,可以將其理解爲類的私有成員變量。

全局變量應該是模塊的私有數據,不能作用對外的接口使用,使用static類型定義,可以有效防止外部文件的非正常訪問,建議定義一個STATIC宏,在調試階段,將STATIC定義爲static,版本發佈時,改爲空,以便於後續的打補丁等操作。

4、防止局部變量與全局變量同名

儘管局部變量和全局變量的作用域不同而不會發生語法錯誤,但容易使人誤解。

5、通訊過程中使用的結構,必須注意字節序

通訊報文中,字節序是一個重要的問題,我司設備使用的CPU類型複雜多樣,大小端、32位/64位的處理器也都有,如果結構會在報文交互過程中使用,必須考慮字節序問題。由於位域在不同字節序下,表現看起來差別更大,所以更需要注意對於這種跨平臺的交互,數據成員發送前,都應該進行主機序到網絡序的轉換;接收時,也必須進行網絡序到主機序的轉換。

6、嚴禁使用未經初始化的變量作爲右值 

在首次使用前初始化變量,初始化的地方離使用的地方越近越好。

7、構造僅有一個模塊或函數可以修改、創建,而其餘有關模塊或函數只訪問的全局變量,防止多個不同模塊或函數都可以修改、創建同一全局變量的現象

降低全局變量耦合度。

8、使用面向接口編程思想,通過 API 訪問數據:如果本模塊的數據需要對外部模塊開放 ,應提供接口函數來設置、獲取,同時注意全局數據的訪問互斥

避免直接暴露內部數據給外部模型使用,是防止模塊間耦合最簡單有效的方法。定義的接口應該有比較明確的意義,比如一個風扇管理功能模塊,有自動和手動工作模式,那麼設置、查詢工作模塊就可以定義接口爲SetFanWorkMode,GetFanWorkMode;查詢轉速就可以定義爲GetFanSpeed;風扇支持節能功能開關,可以定義EnabletFanSavePower等。

9、明確全局變量的初始化順序,避免跨模塊的初始化依賴 

系統啓動階段,使用全局變量前,要考慮到該全局變量在什麼時候初始化,使用全局變量和初始化全局變量,兩者之間的時序關係,誰先誰後,一定要分析清楚,不然後果往往是低級而又災難性的。

10、儘量減少沒有必要的數據類型默認轉換與強制轉換

當進行數據類型強制轉換時,其數據的意義、轉換後的取值等都有可能發生變化,而這些細節若考慮不周,就很有可能留下隱患。

錯誤示例:如下賦值,多數編譯器不產生告警,但值的含義還是稍有變化。

char ch;
unsigned short int exam;
ch = -1;
exam = ch; // 編譯器不產生告警,此時exam爲0xFFFF。

5、宏、常量

1、用宏定義表達式時,要使用完備的括號 

因爲宏只是簡單的代碼替換,不會像函數一樣先將參數計算後,再傳遞。

錯誤示例:如下定義的宏都存在一定的風險

#define RECTANGLE_AREA(a, b) a * b
#define RECTANGLE_AREA(a, b) (a * b)
#define RECTANGLE_AREA(a, b) (a) * (b)

正確示例:

#define RECTANGLE_AREA(a, b) ((a) * (b))

這是因爲:如果定義 #define RECTANGLE_AREA(a, b) a * b  或 #define RECTANGLE_AREA(a, b) (a * b)則 c/RECTANGLE_AREA(a, b)  將擴展成 c/a * b , c  與 b 本應該是除法運算,結果變成了乘法運算,造成錯誤。

如果定義 #define RECTANGLE_AREA(a, b) (a * b)則 RECTANGLE_AREA(c + d, e + f) 將擴展成: (c + d * e + f), d 與 e 先運算,造成錯誤。 

2、將宏所定義的多條表達式放在大括號中

3、使用宏時,不允許參數發生變化

錯誤示例:

#define SQUARE(a) ((a) * (a))
int a = 5;
int b;
b = SQUARE(a++); // 結果:a = 7,即執行了兩次增。

正確示例:

b = SQUARE(a);
a++; // 結果:a = 6,即只執行了一次增。

同時也建議即使函數調用,也不要在參數中做變量變化操作,因爲可能引用的接口函數,在某個版本升級後,變成了一個兼容老版本所做的一個宏,結果可能不可預知。

4、不允許直接使用魔鬼數字 

使用魔鬼數字的弊端:代碼難以理解;如果一個有含義的數字多處使用,一旦需要修改這個數值,代價慘重。

使用明確的物理狀態或物理意義的名稱能增加信息,並能提供單一的維護點。

解決途徑:對於局部使用的唯一含義的魔鬼數字,可以在代碼周圍增加說明註釋,也可以定義局部const變量,變量命名自注釋。對於廣泛使用的數字,必須定義const全局變量/宏;同樣變量/宏命名應是自注釋的。0作爲一個特殊的數字,作爲一般默認值使用沒有歧義時,不用特別定義。

5、除非必要,應儘可能使用函數代替宏

宏對比函數,有一些明顯的缺點:

  • 宏缺乏類型檢查,不如函數調用檢查嚴格;
  • 宏展開可能會產生意想不到的副作用,如#define SQUARE(a) (a) * (a)這樣的定義,如果是SQUARE(i++),就會導致i被加兩次;如果是函數調用double square(double a) {return a * a;}則不會有此副作用;
  • 以宏形式寫的代碼難以調試難以打斷點,不利於定位問題;
  • 宏如果調用的很多,會造成代碼空間的浪費,不如函數空間效率高。

錯誤示例:下面的代碼無法得到想要的結果:

#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))

int MAX_FUNC(int a, int b) {
    return ((a) > (b) ? (a) : (b));
}

int testFunc()
{
    unsigned int a = 1;
    int b = -1;
    printf("MACRO: max of a and b is: %d\n", MAX_MACRO(++a, b));
    printf("FUNC : max of a and b is: %d\n", MAX_FUNC(a, b));
    return 0;
}

上面宏代碼調用中,由於宏缺乏類型檢查,a和b的比較變成無符號數的比較,結果是a < b,所以a只加了一次,所以最終的輸出結果是:

MACRO: max of a and b is: -1
FUNC : max of a and b is: 2

6、常量建議使用 const 定義代替宏

“儘量用編譯器而不用預處理”,因爲#define經常被認爲好象不是語言本身的一部分。看下面的語句:

#define ASPECT_RATIO 1.653

編譯器會永遠也看不到ASPECT_RATIO這個符號名,因爲在源碼進入編譯器之前,它會被預處理程序去掉,於是ASPECT_RATIO不會加入到符號列表中。如果涉及到這個常量的代碼在編譯時報錯,就會很令人費解,因爲報錯信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是在你自己寫的頭文件中定義的,你就會奇怪1.653是從哪裏來的,甚至會花時間跟蹤下去。這個問題也會出現在符號調試器中,因爲同樣地,你所寫的符號名不會出現在符號列表中。
解決這個問題的方案很簡單:不用預處理宏,定義一個常量:

const double ASPECT_RATIO = 1.653;

這種方法很有效,但有兩個特殊情況要注意。首先,定義指針常量時會有點不同。因爲常量定義一般是放在頭文件中(許多源文件會包含它),除了指針所指的類型要定義成const外,重要的是指針也經常要定義成const。例如,要在頭文件中定義一個基於char*的字符串常量,你要寫兩次const:

const char * const authorName = "Scott Meyers";

延伸閱讀材料:關於const和指針的使用,這裏摘錄兩段ISO/IEC 9899(俗稱C99)的描述: 

7、宏定義中儘量不使用 return 、 goto 、 continue 、 break等改變程序流程的語句

如果在宏定義中使用這些改變流程的語句,很容易引起資源泄漏問題,使用者很難自己察覺。

錯誤示例:在某頭文件中定義宏CHECK_AND_RETURN:

#define CHECK_AND_RETURN(cond, ret) {if (cond == NULL_PTR) {return ret;}}
//然後在某函數中使用(只說明問題,代碼並不完整):
pMem1 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem1 , ERR_CODE_XXX)
pMem2 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem2 , ERR_CODE_XXX) /*此時如果pMem2==NULL_PTR,則pMem1未釋放函數就返回了,造成內存泄漏。*/

所以說,類似於CHECK_AND_RETURN這些宏,雖然能使代碼簡潔,但是隱患很大,使用須謹慎。 


6、表達式

1、表達式的值在標準所允許的任何運算次序下都應該是相同的

2、函數調用不要作爲另一個函數的參數使用,否則對於代碼的調試、閱讀都不利

錯誤示例:如下代碼不合理,僅用於說明當函數作爲參數時,由於參數壓棧次數不是代碼可以控制的,可能造成未知的輸出:

int g_var;

int fun1()
{
    g_var += 10;
    return g_var;
}

int fun2()
{
    g_var += 100;
    return g_var;
}

int main(int argc, char *argv[], char *envp[])
{
    g_var = 1;
    printf("func1: %d, func2: %d\n", fun1(), fun2());
    g_var = 1;
    printf("func2: %d, func1: %d\n", fun2(), fun1());
}

上面的代碼,使用斷點調試起來也比較麻煩,閱讀起來也不舒服,所以不要爲了節約代碼行,而寫這種代碼。

3、賦值語句不要寫在 if 等語句中,或者作爲函數的參數使用

因爲if語句中,會根據條件依次判斷,如果前一個條件已經可以判定整個條件,則後續條件語句不會再運行,所以可能導致期望的部分賦值沒有得到運行。

錯誤示例:

int main(int argc, char *argv[], char *envp[])
{
    int a = 0;
    int b;
    if ((a == 0) || ((b = fun1()) > 10))
    {
        printf("a: %d\n", a);
    }
    printf("b: %d\n", b);
}

作用函數參數來使用,參數的壓棧順序不同可能導致結果未知。

4、用括號明確表達式的操作順序,避免過分依賴默認優先級

使用括號強調所使用的操作符,防止因默認的優先級與設計思想不符而導致程序出錯;同時使得代碼更爲清晰可讀,然而過多的括號會分散代碼使其降低了可讀性。

5、賦值操作符不能使用在產生布爾值的表達式上 

示例:

x = y;
if (x != 0)
{
    foo ();
}

不能寫成:

if (( x = y ) != 0)
{
    foo ();
}

或者更壞的:

if (x = y)
{
    foo ();
}

7、註釋

 1、優秀的代碼可 以自我解釋,不通過註釋即可輕易讀懂

優秀的代碼不寫註釋也可輕易讀懂,註釋無法把糟糕的代碼變好,需要很多註釋來解釋的代碼往往存在壞味道,需要重構。

錯誤示例:註釋不能消除代碼的壞味道:

/* 判斷m是否爲素數*/
/* 返回值:: 是素數,: 不是素數*/
int p(int m)
{
    int k = sqrt(m);
    for (int i = 2; i <= k; i++)
        if (m % i == 0)
            break; /* 發現整除,表示m不爲素數,結束遍歷*/
    /* 遍歷中沒有發現整除的情況,返回*/
    if (i > k)
        return 1;
    /* 遍歷中沒有發現整除的情況,返回*/
    else
        return 0;
}

重構代碼後,不需要註釋:

int IsPrimeNumber(int num)
{
    int sqrt_of_num = sqrt (num);
    for (int i = 2; i <= sqrt_of_num; i++)
    {
        if (num % i == 0)
        {
            return FALSE;
        }
    }
    return TRUE;
}

2、註釋的內容要清楚、明瞭,含義準確,防止註釋二義性

有歧義的註釋反而會導致維護者更難看懂代碼,正如帶兩塊表反而不知道準確時間。

3、在代碼的功能、意圖層次上進行註釋,即註釋解釋 代碼難以直接表達的意圖 , 而不是重複描述代碼

註釋的目的是解釋代碼的目的、功能和採用的方法,提供代碼以外的信息,幫助讀者理解代碼,防止沒必要的重複註釋信息。對於實現代碼中巧妙的、晦澀的、有趣的、重要的地方加以註釋。註釋不是爲了名詞解釋(what),而是說明用途(why)。

4、修改代碼時,維護代碼周邊的所有註釋,以保證註釋與代碼的一致性,不再有用的註釋要刪除

不要將無用的代碼留在註釋中,隨時可以從源代碼配置庫中找回代碼;即使只是想暫時排除代碼,也要留個標註,不然可能會忘記處理它。

5、文件頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、作者姓名、工號、內容、功能說明、與其它文件的關係、修改日誌等,頭文件的註釋中還應有函數功能簡要說明

正確示例:下面這段頭文件的頭註釋比較標準,當然,並不侷限於此格式,但上述信息建議要包含在內。

6、函數聲明處註釋描述函數功能、性能及用法,包括輸入和輸出參數、函數返回值、可重入的要求等;定義處詳細描述函數功能和實現要點,如實現的簡要步驟、實現的理由、 設計約束等

重要的、複雜的函數,提供外部使用的接口函數應編寫詳細的註釋。

7、全局變量要有較詳細的註釋,包括對其功能、取值範圍以及存取時注意事項等的說明

正確示例:

/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */ /* 變量作用、含義*/
/* 0 -SUCCESS 1 -GT Table error */
/* 2 -GT error Others -no use */ /* 變量取值範圍*/
/* only function SCCPTranslate() in */
/* this modual can modify it, and other */
/* module can visit it through call */
/* the function GetGTTransErrorCode() */ /* 使用方法*/
BYTE g_GTTranErrorCode;

8、註釋應放在其代碼上方相鄰位置或右方,不可放在下面,如放於上方則需與其上面的代碼用空行隔開,且與下方代碼縮進相同

正確示例:

/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
可按如下形式說明枚舉/數據/聯合結構。
/* sccp interface with sccp user primitive message name */
enum SCCP_USER_PRIMITIVE
{
    N_UNITDATA_IND, /* sccp notify sccp user unit data come */
    N_NOTICE_IND, /* sccp notify user the No.7 network can not transmission this message */
    N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
};

9、對於 switch語句下的case語句,如果因爲特殊情況需要處理完一個case後進入下一個case處理,必須在該case語句處理完、下一個case語句前加上明確的註釋

這樣比較清楚程序編寫者的意圖,有效防止無故遺漏break語句。

case CMD_FWD:
    ProcessFwd();
    /* now jump into c ase CMD_A */
case CMD_A:
    ProcessA();
    break;
//對於中間無處理的連續case,已能較清晰說明意圖,不強制註釋。
switch (cmd_flag)
    {
        case CMD_A:
        case CMD_B:
    {
        ProcessCMD();
        break;
    }
    ……
}

10、避免在註釋中使用縮寫,除非是業界通用或子系統內標準化的縮寫

11、同一產品或項目組統一註釋風格

12、避免在一行代碼或表達式的中間插入註釋

除非必要,不應在代碼或表達中間插入註釋,否則容易使代碼可理解性變差

13、註釋應考慮程序易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達,對於有外籍員工的,由產品確定註釋語言

註釋語言不統一,影響程序易讀性和外觀排版,出於對維護人員的考慮,建議使用中文。

14、文件頭、函數頭、全局常量變量、類型定義的註釋格式採用工具可識別的格式

採用工具可識別的註釋格式,例如doxygen格式,方便工具導出註釋形成幫助文檔。以doxygen格式爲例,文件頭,函數和全部變量的註釋的示例如下:


8、排版與格式

1、程序塊採用縮進風格編寫, 每級縮進爲4個空格

2、相對獨立的程序塊之間、變量說明之後必須加空行 

錯誤示例:如下例子不符合規範。

if (!valid_ni(ni))
{
    // program code
    ...
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

正確示例:

if (!valid_ni(ni))
{
    // program code
    ...
}

repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

3、一條語句不能過長,如不能拆分需要分行寫。一行到底多少字符換行比較合適,產品可以自行確定

對於目前大多數的PC來說,132比較合適(80/132是VTY常見的行寬值);對於新PC寬屏顯示器較多的產品來說,可以設置更大的值。換行時有如下建議:

  • 換行時要增加一級縮進,使代碼可讀性更好;
  • 低優先級操作符處劃分新行;換行時操作符應該也放下來,放在新行首;
  • 換行時建議一個完整的語句放在一行,不要根據字符數斷行。

正確示例:

if ((temp_flag_var == TEST_FLAG)
&&(((temp_counter_var - TEST_COUNT_BEGIN) % TEST_COUNT_MODULE) >= TEST_COUNT_THRESHOLD))
{
    // process code
}

4、多個短語句(包括賦值語句)不允許寫在同一行內 ,即一行只寫一條語句

錯誤示例:

int a = 5; int b= 10; //不好的排版

正確示例:

int a = 5;
int b= 10;

5、if 、 for 、 do 、 while 、 case 、 switch 、 default 等語句獨佔一行

執行語句必須用縮進風格寫,屬於if、for、do、while、case、switch、default等下一個縮進級別;

一般寫if、for、do、while等語句都會有成對出現的„{}‟,對此有如下建議可以參考:if、for、do、while等語句後的執行語句建議增加成對的“{}”;如果if/else配套語句中有一個分支有“{}”,那麼另一個分支即使一行代碼也建議增加“{}”;添加“{”的位置可以在if等語句後,也可以獨立佔下一行;獨立佔下一行時,可以和if在一個縮進級別,也可以在下一個縮進級別;但是如果if語句很長,或者已經有換行,建議“{”使用獨佔一行的寫法。

6、在兩個以上的關鍵字、變量、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格 ; 進行非對等操作時,如果是關係密切的立即操作符(如-> > ),後不應加空格

採用這種鬆散方式編寫代碼的目的是使代碼更加清晰。

在已經非常清晰的語句中沒有必要再留空格,如括號內側(即左括號後面和右括號前面)不需要加空格,多重括號間不必加空格,因爲在C語言中括號已經是最清晰的標誌了。在長語句中,如果需要加的空格非常多,那麼應該保持整體清晰,而在局部不加空格。給操作符留空格時不要連續留兩個以上空格。

正確示例:

1、逗號、分號只在後面加空格。

int a, b, c;

2、比較操作符, 賦值操作符"="、 "+=",算術操作符"+"、"%",邏輯操作符"&&"、"&",位域操作符"<<"、"^"等雙目操作符的前後加空格。 

if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;

3、"!"、"~"、"++"、"--"、"&"(地址操作符)等單目操作符前後不加空格。

*p = 'a'; // 內容操作"*"與內容之間
flag = !is_empty; // 非操作"!"與內容之間
p = &mem; // 地址操作"&" 與內容之間
i++; 

 4、"->"、"."前後不加空格。

p->id = pid; // "->"指針前後不加空格

5、if、for、while、switch等與後面的括號間應加空格,使if等關鍵字更爲突出、明顯。

if (a >= b && c > d)

7、註釋符(包括/**/、//)與註釋內容之間要用一個空格進行分隔

8、源程序中關係較爲緊密的代碼應儘可能相鄰


9、代碼編輯編譯 

1、使用編譯器的最高告警級別,理解所有的告警,通過修改代碼而不是降低告警級別來消除所有告警

編譯器是你的朋友,如果它發出某個告警,這經常說明你的代碼中存在潛在的問題。

2、在產品軟件(項目組)中,要統一編譯開關、靜態檢查選項以及相應告警清除策略

如果必須禁用某個告警,應儘可能單獨局部禁用,並且編寫一個清晰的註釋,說明爲什麼屏蔽。某些語句經編譯/靜態檢查產生告警,但如果你認爲它是正確的,那麼應通過某種手段去掉告警信息。

4、本地構建工具(如 PC-Lint)的配置應該和持續集成的一致

兩者一致,避免經過本地構建的代碼在持續集成上構建失敗

5、 使用版本控制(配置管理)系統,及時簽入通過本地構建的代碼,確保簽入的代碼不會影響構建成功

及時簽入代碼降低集成難度。

6、要小心地使用編輯器提供的塊拷貝功能編程

以上爲自我總結,感興趣的同志,推薦閱讀全文,也就60餘頁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章