整理華為C/C++編碼規範
阿新 • • 發佈:2019-01-24
目 錄
1 排版
2 註釋
3 識別符號命名
4 可讀性
5 變數、結構
6 函式、過程
7 可測性
8 程式效率
9 質量保證
10 程式碼編輯、編譯、審查
11 程式碼測試、維護
12 巨集
1 排版
1-1:程式塊要採用縮排風格編寫,縮排的空格數為4個。
說明:對於由開發工具自動生成的程式碼可以有不一致。
(建議)1-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;
(建議)1-3:較長的語句(>120字元)要分成多行書寫,長表示式要在低優先順序操作符處劃分新行,操作符放在新行之首,劃分出的新行要進行適當的縮排,使排版整齊,語句可讀。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
+ STAT_SIZE_PER_FRAM * sizeof( _UL );
act_task_table[frame_id * STAT_TASK_CHECK_NUMBER + index].occupied
= stat_poi[index].occupied;
act_task_table[taskno].duration_true_or_false
= SYS_get_sccp_statistic_state( stat_item );
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
(建議)1-4:迴圈、判斷等語句中若有較長的表示式或語句(>120字元),則要進行適應的劃分,長表示式要在低優先順序操作符處劃分新行,操作符放在新行之首。
示例:
if ((taskno < max_act_task_number) && (min_act_task_number < taskno)
&& (n7stat_stat_item_valid (stat_item))) {
... // program code
}
for (i = 0, j = 0; (i < BufferKeyword[word_index].word_length)
&& (j < NewKeyword.word_length); i++, j++) {
... // program code
}
for (i = 0, j = 0;
(i < first_word_length) && (j < second_word_length);
i++, j++) {
... // program code
}
(建議)1-5:若函式或過程中的引數較長,則要進行適當的劃分。
示例:
n7stat_str_compare((BYTE *) & stat_object,
(BYTE *) & (act_task_table[taskno].stat_object),
sizeof (_STAT_OBJECT));
n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER
+ index, stat_object );
1-6:不允許把多個短語句寫在一行中,即一行只寫一條語句。
示例:如下例子不符合規範。
rect.length = 0; rect.width = 0;
應如下書寫
rect.length = 0;
rect.width = 0;
1-7:if、for、do、while等語句的執行語句部分無論多少都要加括號{}。
1-8:對齊只使用空格鍵,不使用TAB鍵。(有些工具可以設定TAB為N個空格)
說明:以免用不同的編輯器閱讀程式時,因TAB鍵所設定的空格數目不同而造成程式佈局不整齊,不要使用BC作為編輯器合版本,因為BC會自動將8個空格變為一個TAB鍵,因此使用BC合入的版本大多會將縮排變亂。
1-9:函式或過程的開始、結構的定義及迴圈、判斷等語句中的程式碼都要採用縮排風格,case語句下的情況處理語句也要遵從語句縮排要求。
(建議)1-10:在兩個以上的關鍵字、變數、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格;進行非對等操作時,如果是關係密切的立即操作符(如->),後不應加空格。
說明:採用這種鬆散方式編寫程式碼的目的是使程式碼更加清晰。
由於留空格所產生的清晰性是相對的,所以,在已經非常清晰的語句中沒有必要再留空格,如果語句已足夠清晰則括號內側(即左括號後面和右括號前面)不需要加空格,多重括號間不必加空格,因為在C/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 = !isEmpty; // 非操作"!"與內容之間
p = &mem; // 地址操作"&" 與內容之間
i++; // "++","--"與內容之間
(4)"->"、"."前後不加空格。
p->id = pid; // "->"指標前後不加空格
(5) if、for、while、switch等與後面的括號間應加空格,使if等關鍵字更為突出、明顯。
if (a >= b && c > d)
(建議)1-11:一行程式以小於120字元為宜,不要寫得過長。
2 註釋
(建議)2-1:一般情況下,源程式有效註釋量必須在20%以上。
說明:註釋的原則是有助於對程式的閱讀理解,在該加的地方都加了,註釋不宜太多也不能太少,註釋語言必須準確、易懂、簡潔。
(建議)2-2:說明性檔案(如標頭檔案.h檔案、.inc檔案、.def檔案、編譯說明檔案.cfg等)頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、作者、內容、功能、與其它檔案的關係、修改日誌等,標頭檔案的註釋中還應有函式功能簡要說明。
示例:下面這段標頭檔案的頭註釋比較標準,當然,並不侷限於此格式,但上述資訊建議要包含在內。
/*************************************************
Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
File name: // 檔名
Author: Version: Date: // 作者、版本及完成日期
Description: // 用於詳細說明此程式檔案完成的主要功能,與其他模組
// 或函式的介面,輸出值、取值範圍、含義及引數間的控
// 制、順序、獨立或依賴等關係
Others: // 其它內容的說明
Function List: // 主要函式列表,每條記錄應包括函式名及功能簡要說明
1. ....
History: // 修改歷史記錄列表,每條修改記錄應包括修改日期、修改
// 者及修改內容簡述
1. Date:
Author:
Modification:
2. ...
*************************************************/
(建議)2-3:原始檔頭部應進行註釋,列出:版權說明、版本號、生成日期、作者、模組目的/功能、主要函式及其功能、修改日誌等。
示例:下面這段原始檔的頭註釋比較標準,當然,並不侷限於此格式,但上述資訊建議要包含在內。
/************************************************************
Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
FileName: test.cpp
Author: Version : Date:
Description: // 模組描述
Version: // 版本資訊
Function List: // 主要函式及其功能
1. -------
History: // 歷史修改記錄
<author> <time> <version > <desc>
David 96/10/12 1.0 build this moudle
***********************************************************/
說明:Description一項描述本檔案的內容、功能、內部各部分之間的關係及本檔案與其它檔案關係等。History是修改歷史記錄列表,每條修改記錄應包括修改日期、修改者及修改內容簡述。
(建議)2-4:函式頭部應進行註釋.
(建議)2-5:邊寫程式碼邊註釋,修改程式碼同時修改相應的註釋,以保證註釋與程式碼的一致性。不再有用的註釋要刪除。
2-6:註釋的內容要清楚、明瞭,含義準確,防止註釋二義性。
說明:錯誤的註釋不但無益反而有害。
(建議)2-7:避免在註釋中使用縮寫,特別是非常用縮寫。
說明:在使用縮寫時或之前,應對縮寫進行必要的說明。
(建議)2-8:註釋應與其描述的程式碼相近,對程式碼的註釋應放在其上方或右方(對單條語句的註釋)相鄰位置,不可放在下面,如放於上方則需與其上面的程式碼用空行隔開。
示例:如下例子不符合規範。
例1:
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
例2:
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
/* get replicate sub system index and net indicator */
應如下書寫
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
2-9:對於所有有物理含義的變數、常量,如果其命名不是充分自注釋的,在宣告時都必須加以註釋,說明其物理含義。變數、常量、巨集的註釋應放在其上方相鄰位置或右方。
示例:
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
2-10:資料結構宣告(包括陣列、結構、類、列舉等),如果其命名不是充分自注釋的,必須加以註釋。對資料結構的註釋應放在其上方相鄰位置,不可放在下面;對結構中的每個域的註釋放在此域的右方。
示例:可按如下形式說明列舉/資料/聯合結構。
/* 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*/
};
(建議)2-11:全域性變數要有較詳細的註釋,包括對其功能、取值範圍、哪些函式或過程存取它以及存取時注意事項等的說明。
示例:
/* 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;
2-12:註釋與所描述內容進行同樣的縮排。
說明:可使程式排版整齊,並方便註釋的閱讀與理解。
示例:如下例子,排版不整齊,閱讀稍感不方便。
void example_fun( void ) {
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
}
應改為如下佈局。
void example_fun( void ) {
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
}
(建議)2-14:對變數的定義和分支語句(條件分支、迴圈語句等)必須編寫註釋。
說明:這些語句往往是程式實現某一特定功能的關鍵,對於維護人員來說,良好的註釋幫助更好的理解程式,有時甚至優於看設計文件。
(強制)2-15:對於switch語句下的case語句,如果因為特殊情況需要處理完一個case後進入下一個case處理,必須在該case語句處理完、下一個case語句前加上明確的註釋。
說明:這樣比較清楚程式編寫者的意圖,有效防止無故遺漏break語句。
示例(注意斜體加粗部分):
case CMD_UP:
ProcessUp();
break;
case CMD_DOWN:
ProcessDown();
break;
case CMD_FWD:
ProcessFwd();
if (...)
{
...
break;
}
else
{
ProcessCFW_B(); // now jump into case CMD_A
}
case CMD_A:
ProcessA();
break;
case CMD_B:
ProcessB();
break;
case CMD_C:
ProcessC();
break;
case CMD_D:
ProcessD();
break;
...
2-16:避免在一行程式碼或表示式的中間插入註釋。
說明:除非必要,不應在程式碼或表達中間插入註釋,否則容易使程式碼可理解性變差。
2-17:通過對函式或過程、變數、結構等正確的命名以及合理地組織程式碼的結構,使程式碼成為自注釋的。
說明:清晰準確的函式、變數等的命名,可增加程式碼可讀性,並減少不必要的註釋。
(建議)2-18:在程式碼的功能、意圖層次上進行註釋,提供有用、額外的資訊。
說明:註釋的目的是解釋程式碼的目的、功能和採用的方法,提供程式碼以外的資訊,幫助讀者理解程式碼,防止沒必要的重複註釋資訊。
示例:如下注釋意義不大。
/* if receive_flag is TRUE */
if (receive_flag)
而如下的註釋則給出了額外有用的資訊。
/* if mtp receive a message from links */
if (receive_flag)
(建議)2-19:在程式塊的結束行右方加註釋標記,以表明某程式塊的結束。
說明:當代碼段較長,特別是多重巢狀時,這樣做可以使程式碼更清晰,更便於閱讀。
示例:參見如下例子。
if (...)
{
// program code
while (index < MAX_INDEX)
{
// program code
} /* end of while (index < MAX_INDEX) */ // 指明該條while語句結束
} /* end of if (...)*/ // 指明是哪條if語句結束
2-20:註釋格式儘量統一,建議使用“/* …… */”。
(建議)2-6:註釋應考慮程式易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達。
說明:註釋語言不統一,影響程式易讀性和外觀排版,出於對維護人員的考慮,建議使用中文。
3 識別符號命名
3-1:識別符號的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
說明:較短的單詞可通過去掉“母音”形成縮寫;較長的單詞可取單詞的頭幾個字母形成縮寫;一些單詞有大家公認的縮寫。
示例:如下單詞的縮寫能夠被大家基本認可。
temp 可縮寫為 tmp ;
flag 可縮寫為 flg ;
statistic 可縮寫為 stat ;
increment 可縮寫為 inc ;
message 可縮寫為 msg ;
(建議)3-2:命名中若使用特殊約定或縮寫,則要有註釋說明。
說明:應該在原始檔的開始之處,對檔案中所使用的縮寫或約定,特別是特殊的縮寫,進行必要的註釋說明。
(建議)3-3:自己特有的命名風格,要自始至終保持一致,不可來回變化。
說明:個人的命名風格,在符合所在專案組或產品組的命名規則的前提下,才可使用。(即命名規則中沒有規定到的地方才可有個人命名風格)。
3-4:對於變數命名,禁止取單個字元(如i、j、k...),建議除了要有具體含義外,還能表明其變數型別、資料型別等,但i、j、k作區域性迴圈變數是允許的。
說明:變數,尤其是區域性變數,如果用單個字元表示,很容易敲錯(如i寫成j),而編譯時又檢查不出來,有可能為了這個小小的錯誤而花費大量的查錯時間。
3-5:除非必要,不要用數字或較奇怪的字元來定義識別符號。
示例:如下命名,使人產生疑惑。
#define _EXAMPLE_0_TEST_
#define _EXAMPLE_1_TEST_
void set_sls00( BYTE sls );
應改為有意義的單詞命名
#define _EXAMPLE_UNIT_TEST_
#define _EXAMPLE_ASSERT_TEST_
void set_udt_msg_sls( BYTE sls );
3-6:在同一軟體產品內,應規劃好介面部分識別符號(變數、結構、函式及常量)的命名,防止編譯、連結時產生衝突。
說明:對介面部分的識別符號應該有更嚴格限制,防止衝突。如可規定介面部分的變數與常量之前加上“模組”標識等。
3-7:用正確的反義片語命名具有互斥意義的變數或相反動作的函式等。
說明:下面是一些在軟體中常用的反義片語。
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
cut / paste up / down
示例:
int min_sum;
int max_sum;
int add_user( BYTE *user_name );
int delete_user( BYTE *user_name );
(強制)3-8:除了編譯開關/標頭檔案等特殊應用,應避免使用_EXAMPLE_TEST_之類以下劃線開始和結尾的定義。
4 可讀性
(強制)4-1:注意運算子的優先順序,並用括號明確表示式的操作順序,避免使用預設優先順序。
說明:防止閱讀程式時產生誤解,防止因預設的優先順序與設計思想不符而導致程式出錯。
示例:下列語句中的表示式
word = (high << 8) | low (1)
if ((a | b) && (a & c)) (2)
if ((a | b) < (c & d)) (3)
如果書寫為
high << 8 | low
a | b && a & c
a | b < c & d
由於
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不會出錯,但語句不易理解;
a | b < c & d = a | (b < c) & d,(3)造成了判斷條件出錯。
4-2:避免使用不易理解的數字,用有意義的標識來替代。涉及物理狀態或者含有物理意義的常量,不應直接使用數字,必須用有意義的列舉或巨集來代替。
示例:如下的程式可讀性差。
if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
... // program code
}
應改為如下形式。
#define TRUNK_IDLE 0
#define TRUNK_BUSY 1
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... // program code
}
(建議)4-3:源程式中關係較為緊密的程式碼應儘可能相鄰。
說明:便於程式閱讀和查詢。
示例:以下程式碼佈局不太合理。
rect.length = 10;
char_poi = str;
rect.width = 5;
若按如下形式書寫,可能更清晰一些。
rect.length = 10;
rect.width = 5; // 矩形的長與寬關係較密切,放在一起。
char_poi = str;
4-4:不要使用難懂的技巧性很高的語句,除非很有必要時。
說明:高技巧語句不等於高效率的程式,實際上程式的效率關鍵在於演算法。
示例:如下表達式,考慮不周就可能出問題,也較難理解。
* stat_poi ++ += 1;
* ++ stat_poi += 1;
應分別改為如下。
*stat_poi += 1;
stat_poi++; // 此二語句功能相當於“ * stat_poi ++ += 1; ”
++ stat_poi;
*stat_poi += 1; // 此二語句功能相當於“ * ++ stat_poi += 1; ”
5 變數、結構
(建議)5-1:去掉沒必要的公共變數。
說明:公共變數是增大模組間耦合的原因之一,故應減少沒必要的公共變數以降低模組間的耦合度。
5-2:仔細定義並明確公共變數的含義、作用、取值範圍及公共變數間的關係。
說明:在對變數宣告的同時,應對其含義、作用及取值範圍進行註釋說明,同時若有必要還應說明與其它變數的關係。
5-3:明確公共變數與操作此公共變數的函式或過程的關係,如訪問、修改及建立等。
說明:明確過程操作變數的關係後,將有利於程式的進一步優化、單元測試、系統聯調以及程式碼維護等。這種關係的說明可在註釋或文件中描述。
示例:在原始檔中,可按如下注釋形式說明。
RELATION System_Init Input_Rec Print_Rec Stat_Score
Student Create Modify Access Access
Score Create Modify Access Access, Modify
注:RELATION為操作關係;System_Init、Input_Rec、Print_Rec、Stat_Score為四個不同的函式;Student、Score為兩個全域性變數;Create表示建立,Modify表示修改,Access表示訪問。
其中,函式Input_Rec、Stat_Score都可修改變數Score,故此變數將引起函式間較大的耦合,並可能增加程式碼測試、維護的難度。
5-4:當向公共變數傳遞資料時,要十分小心,防止賦與不合理的值或越界等現象發生。
說明:對公共變數賦值時,若有必要應進行合法性檢查,以提高程式碼的可靠性、穩定性。
5-5:防止區域性變數與公共變數同名。
說明:若使用了較好的命名規則,那麼此問題可自動消除。
5-6:嚴禁使用未經初始化的變數作為右值。
說明:特別是在C/C++中引用未經賦值的指標,經常會引起系統崩潰。
5-7:構造僅有一個模組或函式可以修改、建立,而其餘有關模組或函式只訪問的公共變數,防止多個不同模組或函式都可以修改、建立同一公共變數的現象。
說明:降低公共變數耦合度。
5-8:使用嚴格形式定義的、可移植的資料型別,儘量不要使用與具體硬體或軟體環境關係密切的變數。
說明:使用標準的資料型別,有利於程式的移植, 如用INT32而不是int等。
示例:如下例子(在DOS下BC3.1環境中),在移植時可能產生問題。
5-9:結構的功能要單一,是針對一種事務的抽象。
說明:設計結構時應力爭使結構代表一種現實事務的抽象,而不是同時代表多種。結構中的各元素應代表同一事務的不同側面,而不應把描述沒有關係或關係很弱的不同事務的元素放到同一結構中。
示例:如下結構不太清晰、合理。
typedef struct STUDENT_STRU
{
unsigned char name[8]; /* 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[8]; /* the student teacher's name */
unisgned char
teacher_sex; /* his teacher sex */
} STUDENT;
若改為如下,可能更合理些。
typedef struct TEACHER_STRU
{
unsigned char name[8]; /* teacher name */
unisgned char sex; /* teacher sex, as follows */
/* 0 - FEMALE; 1 - MALE */
} TEACHER;
typedef struct STUDENT_STRU
{
unsigned char name[8]; /* 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;
5-10:不要設計面面俱到、非常靈活的資料結構。
說明:面面俱到、靈活的資料結構反而容易引起誤解和操作困難。
5-11:不同結構間的關係不要過於複雜。
說明:若兩個結構間關係較複雜、密切,那麼應合為一個結構。
示例:如下兩個結構的構造不合理。
typedef struct PERSON_ONE_STRU
{
unsigned char name[8];
unsigned char addr[40];
unsigned char sex;
unsigned char city[15];
} PERSON_ONE;
typedef struct PERSON_TWO_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char tel;
} PERSON_TWO;
由於兩個結構都是描述同一事物的,那麼不如合成一個結構。
typedef struct PERSON_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char sex;
unsigned char addr[40];
unsigned char city[15];
unsigned char tel;
} PERSON;
5-12:結構中元素的個數應適中。若結構中元素個數過多可考慮依據某種原則把元素組成不同的子結構,以減少原結構中元素的個數。
說明:增加結構的可理解性、可操作性和可維護性。
示例:假如認為如上的_PERSON結構元素過多,那麼可如下對之劃分。
typedef struct PERSON_BASE_INFO_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char sex;
} PERSON_BASE_INFO;
typedef struct PERSON_ADDRESS_STRU
{
unsigned char addr[40];
unsigned char city[15];
unsigned char tel;
} PERSON_ADDRESS;
typedef struct PERSON_STRU
{
PERSON_BASE_INFO person_base;
PERSON_ADDRESS person_addr;
} PERSON;
5-13:仔細設計結構中元素的佈局與排列順序,使結構容易理解、節省佔用空間,並減少引起誤用現象。
說明:合理排列結構中元素順序,可節省空間並增加可理解性。
示例:如下結構中的位域排列,將佔較大空間,可讀性也稍差。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
PERSON person;
unsigned int set_flg: 1;
} EXAMPLE;
若改成如下形式,不僅可節省1位元組空間,可讀性也變好了。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
unsigned int set_flg: 1;
PERSON person ;
} EXAMPLE;
5-14:結構的設計要儘量考慮向前相容和以後的版本升級,併為某些未來可能的應用保留餘地(如預留一些空間等)。
說明:軟體向前相容的特性,是軟體產品是否成功的重要標誌之一。如果要想使產品具有較好的前向相容,那麼在產品設計之初就應為以後版本升級保留一定餘地,並且在產品升級時必須考慮前一版本的各種特性。
5-15:留心具體語言及編譯器處理不同資料型別的原則及有關細節。
說明:如在C語言中,static區域性變數將在記憶體“資料區”中生成,而非static區域性變數將在“堆疊”中生成。這些細節對程式質量的保證非常重要。
5-16:程式設計時,要注意資料型別的強制轉換。
說明:當進行資料型別強制轉換時,其資料的意義、轉換後的取值等都有可能發生變化,而這些細節若考慮不周,就很有可能留下隱患。
5-17:對編譯系統預設的資料型別轉換,也要有充分的認識。
示例:如下賦值,多數編譯器不產生告警,但值的含義還是稍有變化。
char chr;
unsigned short int exam;
chr = -1;
exam = chr; // 編譯器不產生告警,此時exam為0xFFFF。
5-18:儘量減少沒有必要的資料型別預設轉換與強制轉換。
5-19:合理地設計資料並使用自定義資料型別,避免資料間進行不必要的型別轉換。
5-20:對自定義資料型別進行恰當命名,使它成為自描述性的,以提高程式碼可讀性。注意其命名方式在同一產品中的統一。
說明:使用自定義型別,可以彌補程式語言提供型別少、資訊量不足的缺點,並能使程式清晰、簡潔。
示例:可參考如下方式宣告自定義資料型別。
下面的宣告可使資料型別的使用簡潔、明瞭。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
下面的宣告可使資料型別具有更豐富的含義。
typedef float DISTANCE;
typedef float SCORE;
(強制)5-21:當宣告用於分散式環境或不同CPU間通訊環境的資料結構時,必須考慮機器的位元組順序、使用的位域及位元組對齊等問題 。
說明:比如Intel CPU與68360 CPU,在處理位域及整數時,其在記憶體存放的“順序”正好相反。
示例:假如有如下短整數及結構。
unsigned short int exam;
typedef struct EXAM_BIT_STRU
{ /* Intel 68360 */
unsigned int A1: 1; /* bit 0 7 */
unsigned int A2: 1; /* bit 1 6 */
unsigned int A3: 1; /* bit 2 5 */
} EXAM_BIT;
如下是Intel CPU生成短整數及位域的方式。
記憶體: 0 1 2 ... (從低到高,以位元組為單位)
exam exam低位元組 exam高位元組
記憶體: 0 bit 1 bit 2 bit ... (位元組的各“位”)
EXAM_BIT A1 A2 A3
如下是68360 CPU生成短整數及位域的方式。
記憶體: 0 1 2 ... (從低到高,以位元組為單位)
exam exam高位元組 exam低位元組
記憶體: 7 bit 6 bit 5 bit ... (位元組的各“位”)
EXAM_BIT A1 A2 A3
說明:在對齊方式下,CPU的執行效率要快得多。
示例:如下圖,當一個long型數(如圖中long1)在記憶體中的位置正好與記憶體的字邊界對齊時,CPU存取這個數只需訪問一次記憶體,而當一個long型數(如圖中的long2)在記憶體中的位置跨越了字邊界時,CPU存取這個數就需要多次訪問記憶體,如i960cx訪問這樣的數需讀記憶體三次(一個BYTE、一個SHORT、一個BYTE,由CPU的微程式碼執行,對軟體透明),所有對齊方式下CPU的執行效率明顯快多了。
1 8 16 24 32
------- ------- ------- -------
| long1 | long1 | long1 | long1 |
------- ------- ------- -------
| | | | long2 |
------- ------- ------- --------
| long2 | long2 | long2 | |
------- ------- ------- --------
| ....
6 函式、過程
6-1:對所呼叫函式的錯誤返回碼要仔細、全面地處理。
6-2:明確函式功能,精確(而不是近似)地實現函式設計。
(強制)6-3:編寫可重入函式時,應注意區域性變數的使用(如編寫C/C++語言的可重入函式時,應使用auto即預設態區域性變數或暫存器變數)。
說明:編寫C/C++語言的可重入函式時,不應使用static區域性變數,否則必須經過特殊處理,才能使函式具有可重入性。
(強制)6-4:編寫可重入函式時,若使用全域性變數,則應通過關中斷、訊號量(即P、V操作)等手段對其加以保護。
說明:若對所使用的全域性變數不加以保護,則此函式就不具有可重入性,即當多個程序呼叫此函式時,很有可能使有關全域性變數變為不可知狀態。
示例:假設Exam是int型全域性變數,函式Squre_Exam返回Exam平方值。那麼如下函式不具有可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
此函式若被多個程序呼叫的話,其結果可能是未知的,因為當(**)語句剛執行完後,另外一個使用本函式的程序可能正好被啟用,那麼當新啟用的程序執行到此函式時,將使Exam賦與另一個不同的para值,所以當控制重新回到“temp = Square_Exam( )”後,計算出的temp很可能不是預想中的結果。此函式應如下改進。
unsigned int example( int para )
{
unsigned int temp;
[申請訊號量操作] // 若申請不到“訊號量”,說明另外的程序正處於
Exam = para; // 給Exam賦值並計算其平方過程中(即正在使用此
temp = Square_Exam( ); // 訊號),本程序必須等待其釋放訊號後,才可繼
[釋放訊號量操作] // 續執行。若申請到訊號,則可繼續執行,但其
// 它程序必須等待本程序釋放訊號量後,才能再使
// 用本訊號。
return temp;
}
6-5:在同一專案組應明確規定對介面函式引數的合法性檢查應由函式的呼叫者負責還是由介面函式本身負責,預設是由函式呼叫者負責。
說明:對於模組間介面函式的引數的合法性檢查這一問題,往往有兩個極端現象,即:要麼是呼叫者和被呼叫者對引數均不作合法性檢查,結果就遺漏了合法性檢查這一必要的處理過程,造成問題隱患;要麼就是呼叫者和被呼叫者均對引數進行合法性檢查,這種情況雖不會造成問題,但產生了冗餘程式碼,降低了效率。
6-6:防止將函式的引數作為工作變數。
說明:將函式的引數作為工作變數,有可能錯誤地改變引數內容,所以很危險。對必須改變的引數,最好先用區域性變數代之,最後再將該區域性變數的內容賦給該引數。
示例:下函式的實現不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0;
for (count = 0; count < num; count++)
{
*sum += data[count]; // sum成了工作變數,不太好。
}
}
若改為如下,則更好些。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count ;
int sum_temp;
sum_temp = 0;
for (count = 0; count < num; count ++)
{
sum_temp += data[count];
}
*sum = sum_temp;
}
(強制)6-7:函式的規模儘量限制在200行以內。
說明:不包括註釋和空格行。
6-8:一個函式僅完成一件功能。
6-9:為簡單功能編寫函式。
說明:雖然為僅用一兩行就可完成的功能去編函式好象沒有必要,但用函式可使功能明確化,增加程式可讀性,亦可方便維護、測試。
示例:如下語句的功能不很明顯。
value = ( a > b ) ? a : b ;
改為如下就很清晰了。
int max (int a, int b)
{
return ((a > b) ? a : b);
}
value = max (a, b);
或改為如下。
#define MAX (a, b) (((a) > (b)) ? (a) : (b))
value = MAX (a, b);
6-10:不要設計多用途面面俱到的函式。
說明:多功能集於一身的函式,很可能使函式的理解、測試、維護等變得困難。
6-11:函式的功能應該是可以預測的,也就是隻要輸入資料相同就應產生同樣的輸出。
說明:帶有內部“儲存器”的函式的功能可能是不可預測的,因為它的輸出可能取決於內部儲存器(如某標記)的狀態。這樣的函式既不易於理解又不利於測試和維護。在C/C++語言中,函式的static區域性變數是函式的內部儲存器,有可能使函式的功能不可預測,然而,當某函式的返回值為指標型別時,則必須是STATIC的區域性變數的地址作為返回值,若為AUTO類,則返回為指標。
示例:如下函式,其返回值(即功能)是不可預測的。
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;
}
6-12:儘量不要編寫依賴於其他函式內部實現的函式。
說明:此條為函式獨立性的基本要求。由於目前大部分高階語言都是結構化的,所以通過具體語言的語法要求與編譯器功能,基本就可以防止這種情況發生。但在組合語言中,由於其靈活性,很可能使函數出現這種情況。
示例:如下是在DOS下TASM的彙編程式例子。過程Print_Msg的實現依賴於Input_Msg的具體實現,這種程式是非結構化的,難以維護、修改。
... // 程式程式碼
proc Print_Msg // 過程(函式)Print_Msg
... // 程式程式碼
jmp LABEL
... // 程式程式碼
endp
proc Input_Msg // 過程(函式)Input_Msg
... // 程式程式碼
LABEL:
... // 程式程式碼
endp
6-13:避免設計多引數函式,不使用的引數從介面中去掉。
說明:目的減少函式間介面的複雜度。
(建議)6-14:非排程函式應減少或防止控制引數,儘量只使用資料引數。
說明:本建議目的是防止函式間的控制耦合。排程函式是指根據輸入的訊息型別或控制命令,來啟動相應的功能實體(即函式或過程),而本身並不完成具體功能。控制引數是指改變函式功能行為的引數,即函式要根據此引數來決定具體怎樣工作。非排程函式的控制引數增加了函式間的控制耦合,很可能使函式間的耦合度增大,並使函式的功能不唯一。
示例:如下函式構造不太合理。
int add_sub( int a, int b, unsigned char add_sub_flg )
{
if (add_sub_flg == INTEGER_ADD)
{
return (a + b);
}
else
{
return (a b);
}
}
不如分為如下兩個函式清晰。
int add( int a, int b )
{
return (a + b);
}
int sub( int a, int b )
{
return (a b);
}
6-15:檢查函式所有引數輸入的有效性。
6-16:檢查函式所有非引數輸入的有效性,如資料檔案、公共變數等。
說明:函式的輸入主要有兩種:一種是引數輸入;另一種是全域性變數、資料檔案的輸入,即非引數輸入。函式在使用輸入之前,應進行必要的檢查。
6-17:函式名應準確描述函式的功能。
6-18:使用動賓片語為執行某操作的函式命名。如果是OOP方法,可以只有動詞(名詞是物件本身)。
示例:參照如下方式命名函式。
void print_record( unsigned int rec_ind ) ;
int input_record( void ) ;
unsigned char get_current_color( void ) ;
(建議)6-19:避免使用無意義或含義不清的動詞為函式命名。
說明:避免用含義不清的動詞如process、handle等為函式命名,因為這些動詞並沒有說明要具體做什麼。
(建議)6-20:函式的返回值要清楚、明瞭,讓使用者不容易忽視錯誤情況。
說明:函式的每種出錯返回值的意義要清晰、明瞭、準確,防止使用者誤用、理解錯誤或忽視錯誤返回碼。
6-21:除非必要,最好不要把與函式返回值型別不同的變數,以編譯系統預設的轉換方式或強制的轉換方式作為返回值返回。
6-22:讓函式在呼叫點顯得易懂、容易理解。
6-23:在呼叫函式填寫引數時,應儘量減少沒有必要的預設資料型別轉換或強制資料型別轉換。
說明:因為資料型別轉換或多或少存在危險。
6-24:避免函式中不必要語句,防止程式中的垃圾程式碼。
說明:程式中的垃圾程式碼不僅佔用額外的空間,而且還常常影響程式的功能與效能,很可能給程式的測試、維護等造成不必要的麻煩。
6-25:防止把沒有關聯的語句放到一個函式中。
說明:防止函式或過程內出現隨機內聚。隨機內聚是指將沒有關聯或關聯很弱的語句放到同一個函式或過程中。隨機內聚給函式或過程的維護、測試及以後的升級等造成了不便,同時也使函式或過程的功能不明確。使用隨機內聚函式,常常容易出現在一種應用場合需要改進此函式,而另一種應用場合又不允許這種改進,從而陷入困境。
在程式設計時,經常遇到在不同函式中使用相同的程式碼,許多開發人員都願把這些程式碼提出來,並構成一個新函式。若這些程式碼關聯較大並且是完成一個功能的,那麼這種構造是合理的,否則這種構造將產生隨機內聚的函式。
示例:如下函式就是一種隨機內聚。
void Init_Var( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的長與寬 */
Point.x = 10;
Point.y = 10; /* 初始化“點”的座標 */
}
矩形的長、寬與點的座標基本沒有任何關係,故以上函式是隨機內聚。
應如下分為兩個函式:
void Init_Rect( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的長與寬 */
}
void Init_Point( void )
{
Point.x = 10;
Point.y = 10; /* 初始化“點”的座標 */
}
6-26:如果多段程式碼重複做同一件事情,那麼在函式的劃分上可能存在問題。
說明:若此段程式碼各語句之間有實質性關聯並且是完成同一件功能的,那麼可考慮把此段程式碼構造成一個新的函式。
6-27:功能不明確較小的函式,特別是僅有一個上級函式呼叫它時,應考慮把它合併到上級函式中,而不必單獨存在。
說明:模組中函式劃分的過多,一般會使函式間的介面變得複雜。所以過小的函式,特別是扇入很低的或功能不明確的函式,不值得單獨存在。
6-28:設計高扇入、合理扇出(小於7)的函式。
說明:扇出是指一個函式直接呼叫(控制)其它函式的數目,而扇入是指有多少上級函式呼叫它。
扇出過大,表明函式過分複雜,需要控制和協調過多的下級函式;而扇出過小,如總是1,表明函式的呼叫層次可能過多,這樣不利程式閱讀和函式結構的分析,並且程式執行時會對系統資源如堆疊空間等造成壓力。函式較合理的扇出(排程函式除外)通常是3-5。扇出太大,一般是由於缺乏中間層次,可適當增加中間層次的函式。扇出太小,可把下級函式進一步分解多個函式,或合併到上級函式中。當然分解或合併函式時,不能改變要實現的功能,也不能違背函式間的獨立性。
扇入越大,表明使用此函式的上級函式越多,這樣的函式使用效率高,但不能違背函式間的獨立性而單純地追求高扇入。公共模組中的函式及底層函式應該有較高的扇入。
較良好的軟體結構通常是頂層函式的扇出較高,中層函式的扇出較少,而底層函式則扇入到公共模組中。
(強制)6-29:除非為某些演算法或功能的實現方便, 否則禁用遞迴呼叫。
說明:遞迴呼叫特別是函式間的遞迴呼叫(如A->B->C->A),影響程式的可理解性;遞迴呼叫一般都佔用較多的系統資源(如棧空間);遞迴呼叫對程式的測試有一定影響。故除非為某些演算法或功能的實現方便,應減少沒必要的遞迴呼叫。
6-30:仔細分析模組的功能及效能需求,並進一步細分,同時若有必要畫出有關資料流圖,據此來進行模組的函式劃分與組織。
說明:函式的劃分與組織是模組的實現過程中很關鍵的步驟,如何劃分出合理的函式結構,關係到模組的最終效率和可維護性、可測性等。根據模組的功能圖或/及資料流圖映射出函式結構是常用方法之一。
6-31:改進模組中函式的結構,降低函式間的耦合度,並提高函式的獨立性以及程式碼可讀性、效率和可維護性。優化函式結構時,要遵守以下原則:
(1)不能影響模組功能的實現。
(2)仔細考查模組或函數出錯處理及模組的效能要求並進行完善。
(3)通過分解或合併函式來改進軟體結構。
(4)考查函式的規模,過大的要進行分解。
(5)降低函式間介面的複雜度。
(6)不同層次的函式呼叫要有較合理的扇入、扇出。
(7)函式功能應可預測。
(8)提高函式內聚。(單一功能的函式內聚最高)
說明:對初步劃分後的函式結構應進行改進、優化,使之更為合理。
(強制)6-32:在多工作業系統的環境下程式設計,要注意函式可重入性的構造。
說明:可重入性是指函式可以被多個任務程序呼叫。在多工作業系統中,函式是否具有可重入性是非常重要的,因為這是多個程序可以共用此函式的必要條件。另外,編譯器是否提供可重入函式庫,與它所服務的作業系統有關,只有作業系統是多工時,編譯器才有可能提供可重入函式庫。如DOS下BC和MSC等就不具備可重入函式庫,因為DOS是單使用者單任務作業系統。
(建議)6-33:避免使用BOOL引數。
說明:原因有二,其一是BOOL引數值無意義,TURE/FALSE的含義是非常模糊的,在呼叫時很難知道該引數到底傳達的是什麼意思;其二是BOOL引數值不利於擴充。還有NULL也是一個無意義的單詞。
6-34:對於提供了返回值的函式,在引用時最好使用其返回值。
(建議)6-35:當一個過程(函式)中對較長變數(一般是結構的成員)有較多引用時,可以用一個意義相當的巨集代替。
說明:這樣可以增加程式設計效率和程式的可讀性。
示例:在某過程中較多引用TheReceiveBuffer[FirstSocket].byDataPtr,
則可以通過以下巨集定義來代替:
# define pSOCKDATA TheReceiveBuffer[FirstScoket].byDataPtr
7 可測性
7-1:在同一專案組或產品組內,要有一套統一的為整合測試與系統聯調準備的調測開關及相應列印函式,並且要有詳細的說明。
說明:本規則是針對專案組或產品組的。
7-2:在同一專案組或產品組內,調測打印出的資訊串的格式要有統一的形式。資訊串中至少要有所在模組名(或原始檔名)及行號。
說明:統一的調測資訊格式便於整合測試。
7-3:程式設計的同時要為單元測試選擇恰當的測試點,並仔細構造測試程式碼、測試用例,同時給出明確的註釋說明。測試程式碼部分應作為(模組中的)一個子模組,以方便測試程式碼在模組中的安裝與拆卸(通過調測開關)。
說明:為單元測試而準備。
7-4:在進行整合測試/系統聯調之前,要構造好測試環境、測試專案及測試用例,同時仔細分析並優化測試用例,以提高測試效率。
說明:好的測試用例應儘可能模擬出程式所遇到的邊界值、各種複雜環境及一些極端情況等。
7-5:使用斷言來發現軟體問題,提高程式碼可測性。
說明:斷言是對某種假設條件進行檢查(可理解為若條件成立則無動作,否則應報告),它可以快速發現並定位軟體問題,同時對系統錯誤進行自動報警。斷言可以對在系統中隱藏很深,用其它手段極難發現的問題進行定位,從而縮短軟體問題定位時間,提高系統的可測性。實際應用時,可根據具體情況靈活地設計斷言。
示例:下面是C語言中的一個斷言,用巨集來設計的。(其中NULL為0L)
#ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
void exam_assert( char * file_name, unsigned int line_no )
{
printf( "/n[EXAM]Assert failed: %s, line %u/n",
file_name, line_no );
abort( );
}
#define EXAM_ASSERT( condition )
if (condition) // 若條件成立,則無動作
NULL;
else // 否則報告
exam_assert( __FILE__, __LINE__ )
#else // 若不使用斷言測試
#define EXAM_ASSERT(condition) NULL
#endif /* end of ASSERT */
7-6:用斷言來檢查程式正常執行時不應發生但在調測時有可能發生的非法情況。
7-7:不能用斷言來檢查最終產品肯定會出現且必須處理的錯誤情況。
說明:斷言是用來處理不應該發生的錯誤情況的,對於可能會發生的且必須處理的情況要寫防錯程式,而不是斷言。如某模組收到其它模組或鏈路上的訊息後,要對訊息的合理性進行檢查,此過程為正常的錯誤檢查,不能用斷言來實現。
7-8:對較複雜的斷言加上明確的註釋。
說明:為複雜的斷言加註釋,可澄清斷言含義並減少不必要的誤用。
7-9:用斷言確認函式的引數。
示例:假設某函式引數中有一個指標,那麼使用指標前可對它檢查,如下。
int exam_fun( unsigned char *str )
{
EXAM_ASSERT( str != NULL ); // 用斷言檢查“假設指標不為空”這個條件
... //other program code
}
7-10:用斷言保證沒有定義的特性或功能不被使用。
示例:假設某通訊模組在設計時,準備提供“無連線”和“連線” 這兩種業務。但當前的版本中僅實現了“無連線”業務,且在此版本的正式發行版中,使用者(上層模組)不應產生“連線”業務的請求,那麼在測試時可用斷言檢查使用者是否使用“連線”業務。如下。
#define EXAM_CONNECTIONLESS 0 // 無連線業務
#define EXAM_CONNECTION 1 // 連線業務
int msg_process( EXAM_MESSAGE *msg )
{
unsigned char service; /* message service class */
EXAM_ASSERT( msg != NULL );
service = get_msg_service_class( msg );
EXAM_ASSERT( service != EXAM_CONNECTION ); // 假設不使用連線業務
... //other program code
}
(建議)7-11:用斷言對程式開發環境(OS/Compiler/Hardware)的假設進行檢查。
說明:程式執行時所需的軟硬體環境及配置要求,不能用斷言來檢查,而必須由一段專門程式碼處理。用斷言僅可對程式開發環境中的假設及所配置的某版本軟硬體是否具有某種功能的假設進行檢查。如某網絡卡是否在系統執行環境中配置了,應由程式中正式程式碼來檢查;而此網絡卡是否具有某設想的功能,則可由斷言來檢查。
對編譯器提供的功能及特性假設可用斷言檢查,原因是軟體最終產品(即執行程式碼或機器碼)與編譯器已沒有任何直接關係,即軟體執行過程中(注意不是編譯過程中)不會也不應該對編譯器的功能提出任何需求。
示例:用斷言檢查編譯器的int型資料佔用的記憶體空間是否為2,如下。
EXAM_ASSERT( sizeof( int ) == 2 );
(強制)7-12:正式軟體產品中應把斷言及其它調測程式碼去掉(即把有關的調測開關關掉)。
說明:加快軟體執行速度。
(強制)7-13:在軟體系統中設定與取消有關測試手段,不能對軟體實現的功能等產生影響。
說明:即有測試程式碼的軟體和關掉測試程式碼的軟體,在功能行為上應一致。
7-14:用調測開關來切換軟體的DEBUG版和正式版,而不要同時存在正式版本和DEBUG版本的不同原始檔,以減少維護的難度。
7-15:軟體的DEBUG版本和發行版本應該統一維護,不允許分家,並且要時刻注意保證兩個版本在實現功能上的一致性。
7-16:在編寫程式碼之前,應預先設計好程式除錯與測試的方法和手段,並設計好各種調測開關及相應測試程式碼如列印函式等。
說明:程式的除錯與測試是軟體生存週期中很重要的一個階段,如何對軟體進行較全面、高率的測試並儘可能地找出軟體中的錯誤就成為很關鍵的問題。因此在編寫原始碼之前,除了要有一套比較完善的測試計劃外,還應設計出一系列程式碼測試手段,為單元測試、整合測試及系統聯調提供方便。
7-17:調測開關應分為不同級別和型別。
說明:調測開關的設定及分類應從以下幾方面考慮:針對模組或系統某部分程式碼的調測;針對模組或系統某功能的調測;出於某種其它目的,如對效能、容量等的測試。這樣做便於軟體功能的調測,並且便於模組的單元測試、系統聯調等。
(建議)7-18:編寫防錯程式,然後在處理錯誤之後可用斷言宣佈發生錯誤。
示例:假如某模組收到通訊鏈路上的訊息,則應對訊息的合法性進行檢查,若訊息類別不是通訊協議中規定的,則應進行出錯處理,之後可用斷言報告,如下例。
#ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
/* Notice: this function does not call 'abort' to exit program */
void assert_report( char * file_name, unsigned int line_no )
{
printf( "/n[EXAM]Error Report: %s, line %u/n",
file_name, line_no );
}
#define ASSERT_REPORT( condition )
if ( condition ) // 若條件成立,則無動作
NULL;
else // 否則報告
assert_report ( __FILE__, __LINE__ )
#else // 若不使用斷言測試
#define ASSERT_REPORT( condition ) NULL
#endif /* end of ASSERT */
int msg_handle( unsigned char msg_name, unsigned char * msg )
{
switch( msg_name )
{
case MSG_ONE:
... // 訊息MSG_ONE處理
return MSG_HANDLE_SUCCESS;
... // 其它合法訊息處理
default:
... // 訊息出錯處理
ASSERT_REPORT( FALSE ); // “合法”訊息不成立,報告
return MSG_HANDLE_ERROR;
}
}
8 程式效率
8-1:程式設計時要經常注意程式碼的效率。
說明:程式碼效率分為全域性效率、區域性效率、時間效率及空間效率。全域性效率是站在整個系統的角度上的系統效率;區域性效率是站在模組或函式角度上的效率;時間效率是程式處理輸入任務所需的時間長短;空間效率是程式所需記憶體空間,如機器程式碼空間大小、資料空間大小、棧空間大小等。
8-2:在保證軟體系統的正確性、穩定性、可讀性及可測性的前提下,提高程式碼效率。
說明:不能一味地追求程式碼效率,而對軟體的正確性、穩定性、可讀性及可測性造成影響。
(強制)8-3:區域性效率應為全域性效率服務,不能因為提高區
1 排版
2 註釋
3 識別符號命名
4 可讀性
5 變數、結構
6 函式、過程
7 可測性
8 程式效率
9 質量保證
10 程式碼編輯、編譯、審查
11 程式碼測試、維護
12 巨集
1 排版
1-1:程式塊要採用縮排風格編寫,縮排的空格數為4個。
說明:對於由開發工具自動生成的程式碼可以有不一致。
(建議)1-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;
(建議)1-3:較長的語句(>120字元)要分成多行書寫,長表示式要在低優先順序操作符處劃分新行,操作符放在新行之首,劃分出的新行要進行適當的縮排,使排版整齊,語句可讀。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
+ STAT_SIZE_PER_FRAM * sizeof( _UL );
act_task_table[frame_id * STAT_TASK_CHECK_NUMBER + index].occupied
= stat_poi[index].occupied;
act_task_table[taskno].duration_true_or_false
= SYS_get_sccp_statistic_state( stat_item );
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
(建議)1-4:迴圈、判斷等語句中若有較長的表示式或語句(>120字元),則要進行適應的劃分,長表示式要在低優先順序操作符處劃分新行,操作符放在新行之首。
示例:
if ((taskno < max_act_task_number) && (min_act_task_number < taskno)
&& (n7stat_stat_item_valid (stat_item))) {
... // program code
}
for (i = 0, j = 0; (i < BufferKeyword[word_index].word_length)
&& (j < NewKeyword.word_length); i++, j++) {
... // program code
}
for (i = 0, j = 0;
(i < first_word_length) && (j < second_word_length);
i++, j++) {
... // program code
}
(建議)1-5:若函式或過程中的引數較長,則要進行適當的劃分。
示例:
n7stat_str_compare((BYTE *) & stat_object,
(BYTE *) & (act_task_table[taskno].stat_object),
sizeof (_STAT_OBJECT));
n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER
+ index, stat_object );
1-6:不允許把多個短語句寫在一行中,即一行只寫一條語句。
示例:如下例子不符合規範。
rect.length = 0; rect.width = 0;
應如下書寫
rect.length = 0;
rect.width = 0;
1-7:if、for、do、while等語句的執行語句部分無論多少都要加括號{}。
1-8:對齊只使用空格鍵,不使用TAB鍵。(有些工具可以設定TAB為N個空格)
說明:以免用不同的編輯器閱讀程式時,因TAB鍵所設定的空格數目不同而造成程式佈局不整齊,不要使用BC作為編輯器合版本,因為BC會自動將8個空格變為一個TAB鍵,因此使用BC合入的版本大多會將縮排變亂。
1-9:函式或過程的開始、結構的定義及迴圈、判斷等語句中的程式碼都要採用縮排風格,case語句下的情況處理語句也要遵從語句縮排要求。
(建議)1-10:在兩個以上的關鍵字、變數、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格;進行非對等操作時,如果是關係密切的立即操作符(如->),後不應加空格。
說明:採用這種鬆散方式編寫程式碼的目的是使程式碼更加清晰。
由於留空格所產生的清晰性是相對的,所以,在已經非常清晰的語句中沒有必要再留空格,如果語句已足夠清晰則括號內側(即左括號後面和右括號前面)不需要加空格,多重括號間不必加空格,因為在C/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 = !isEmpty; // 非操作"!"與內容之間
p = &mem; // 地址操作"&" 與內容之間
i++; // "++","--"與內容之間
(4)"->"、"."前後不加空格。
p->id = pid; // "->"指標前後不加空格
(5) if、for、while、switch等與後面的括號間應加空格,使if等關鍵字更為突出、明顯。
if (a >= b && c > d)
(建議)1-11:一行程式以小於120字元為宜,不要寫得過長。
2 註釋
(建議)2-1:一般情況下,源程式有效註釋量必須在20%以上。
說明:註釋的原則是有助於對程式的閱讀理解,在該加的地方都加了,註釋不宜太多也不能太少,註釋語言必須準確、易懂、簡潔。
(建議)2-2:說明性檔案(如標頭檔案.h檔案、.inc檔案、.def檔案、編譯說明檔案.cfg等)頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、作者、內容、功能、與其它檔案的關係、修改日誌等,標頭檔案的註釋中還應有函式功能簡要說明。
示例:下面這段標頭檔案的頭註釋比較標準,當然,並不侷限於此格式,但上述資訊建議要包含在內。
/*************************************************
Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
File name: // 檔名
Author: Version: Date: // 作者、版本及完成日期
Description: // 用於詳細說明此程式檔案完成的主要功能,與其他模組
// 或函式的介面,輸出值、取值範圍、含義及引數間的控
// 制、順序、獨立或依賴等關係
Others: // 其它內容的說明
Function List: // 主要函式列表,每條記錄應包括函式名及功能簡要說明
1. ....
History: // 修改歷史記錄列表,每條修改記錄應包括修改日期、修改
// 者及修改內容簡述
1. Date:
Author:
Modification:
2. ...
*************************************************/
(建議)2-3:原始檔頭部應進行註釋,列出:版權說明、版本號、生成日期、作者、模組目的/功能、主要函式及其功能、修改日誌等。
示例:下面這段原始檔的頭註釋比較標準,當然,並不侷限於此格式,但上述資訊建議要包含在內。
/************************************************************
Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
FileName: test.cpp
Author: Version : Date:
Description: // 模組描述
Version: // 版本資訊
Function List: // 主要函式及其功能
1. -------
History: // 歷史修改記錄
<author> <time> <version > <desc>
David 96/10/12 1.0 build this moudle
***********************************************************/
說明:Description一項描述本檔案的內容、功能、內部各部分之間的關係及本檔案與其它檔案關係等。History是修改歷史記錄列表,每條修改記錄應包括修改日期、修改者及修改內容簡述。
(建議)2-4:函式頭部應進行註釋.
(建議)2-5:邊寫程式碼邊註釋,修改程式碼同時修改相應的註釋,以保證註釋與程式碼的一致性。不再有用的註釋要刪除。
2-6:註釋的內容要清楚、明瞭,含義準確,防止註釋二義性。
說明:錯誤的註釋不但無益反而有害。
(建議)2-7:避免在註釋中使用縮寫,特別是非常用縮寫。
說明:在使用縮寫時或之前,應對縮寫進行必要的說明。
(建議)2-8:註釋應與其描述的程式碼相近,對程式碼的註釋應放在其上方或右方(對單條語句的註釋)相鄰位置,不可放在下面,如放於上方則需與其上面的程式碼用空行隔開。
示例:如下例子不符合規範。
例1:
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
例2:
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
/* get replicate sub system index and net indicator */
應如下書寫
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
2-9:對於所有有物理含義的變數、常量,如果其命名不是充分自注釋的,在宣告時都必須加以註釋,說明其物理含義。變數、常量、巨集的註釋應放在其上方相鄰位置或右方。
示例:
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
2-10:資料結構宣告(包括陣列、結構、類、列舉等),如果其命名不是充分自注釋的,必須加以註釋。對資料結構的註釋應放在其上方相鄰位置,不可放在下面;對結構中的每個域的註釋放在此域的右方。
示例:可按如下形式說明列舉/資料/聯合結構。
/* 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*/
};
(建議)2-11:全域性變數要有較詳細的註釋,包括對其功能、取值範圍、哪些函式或過程存取它以及存取時注意事項等的說明。
示例:
/* 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;
2-12:註釋與所描述內容進行同樣的縮排。
說明:可使程式排版整齊,並方便註釋的閱讀與理解。
示例:如下例子,排版不整齊,閱讀稍感不方便。
void example_fun( void ) {
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
}
應改為如下佈局。
void example_fun( void ) {
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
}
(建議)2-14:對變數的定義和分支語句(條件分支、迴圈語句等)必須編寫註釋。
說明:這些語句往往是程式實現某一特定功能的關鍵,對於維護人員來說,良好的註釋幫助更好的理解程式,有時甚至優於看設計文件。
(強制)2-15:對於switch語句下的case語句,如果因為特殊情況需要處理完一個case後進入下一個case處理,必須在該case語句處理完、下一個case語句前加上明確的註釋。
說明:這樣比較清楚程式編寫者的意圖,有效防止無故遺漏break語句。
示例(注意斜體加粗部分):
case CMD_UP:
ProcessUp();
break;
case CMD_DOWN:
ProcessDown();
break;
case CMD_FWD:
ProcessFwd();
if (...)
{
...
break;
}
else
{
ProcessCFW_B(); // now jump into case CMD_A
}
case CMD_A:
ProcessA();
break;
case CMD_B:
ProcessB();
break;
case CMD_C:
ProcessC();
break;
case CMD_D:
ProcessD();
break;
...
2-16:避免在一行程式碼或表示式的中間插入註釋。
說明:除非必要,不應在程式碼或表達中間插入註釋,否則容易使程式碼可理解性變差。
2-17:通過對函式或過程、變數、結構等正確的命名以及合理地組織程式碼的結構,使程式碼成為自注釋的。
說明:清晰準確的函式、變數等的命名,可增加程式碼可讀性,並減少不必要的註釋。
(建議)2-18:在程式碼的功能、意圖層次上進行註釋,提供有用、額外的資訊。
說明:註釋的目的是解釋程式碼的目的、功能和採用的方法,提供程式碼以外的資訊,幫助讀者理解程式碼,防止沒必要的重複註釋資訊。
示例:如下注釋意義不大。
/* if receive_flag is TRUE */
if (receive_flag)
而如下的註釋則給出了額外有用的資訊。
/* if mtp receive a message from links */
if (receive_flag)
(建議)2-19:在程式塊的結束行右方加註釋標記,以表明某程式塊的結束。
說明:當代碼段較長,特別是多重巢狀時,這樣做可以使程式碼更清晰,更便於閱讀。
示例:參見如下例子。
if (...)
{
// program code
while (index < MAX_INDEX)
{
// program code
} /* end of while (index < MAX_INDEX) */ // 指明該條while語句結束
} /* end of if (...)*/ // 指明是哪條if語句結束
2-20:註釋格式儘量統一,建議使用“/* …… */”。
(建議)2-6:註釋應考慮程式易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達。
說明:註釋語言不統一,影響程式易讀性和外觀排版,出於對維護人員的考慮,建議使用中文。
3 識別符號命名
3-1:識別符號的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解。
說明:較短的單詞可通過去掉“母音”形成縮寫;較長的單詞可取單詞的頭幾個字母形成縮寫;一些單詞有大家公認的縮寫。
示例:如下單詞的縮寫能夠被大家基本認可。
temp 可縮寫為 tmp ;
flag 可縮寫為 flg ;
statistic 可縮寫為 stat ;
increment 可縮寫為 inc ;
message 可縮寫為 msg ;
(建議)3-2:命名中若使用特殊約定或縮寫,則要有註釋說明。
說明:應該在原始檔的開始之處,對檔案中所使用的縮寫或約定,特別是特殊的縮寫,進行必要的註釋說明。
(建議)3-3:自己特有的命名風格,要自始至終保持一致,不可來回變化。
說明:個人的命名風格,在符合所在專案組或產品組的命名規則的前提下,才可使用。(即命名規則中沒有規定到的地方才可有個人命名風格)。
3-4:對於變數命名,禁止取單個字元(如i、j、k...),建議除了要有具體含義外,還能表明其變數型別、資料型別等,但i、j、k作區域性迴圈變數是允許的。
說明:變數,尤其是區域性變數,如果用單個字元表示,很容易敲錯(如i寫成j),而編譯時又檢查不出來,有可能為了這個小小的錯誤而花費大量的查錯時間。
3-5:除非必要,不要用數字或較奇怪的字元來定義識別符號。
示例:如下命名,使人產生疑惑。
#define _EXAMPLE_0_TEST_
#define _EXAMPLE_1_TEST_
void set_sls00( BYTE sls );
應改為有意義的單詞命名
#define _EXAMPLE_UNIT_TEST_
#define _EXAMPLE_ASSERT_TEST_
void set_udt_msg_sls( BYTE sls );
3-6:在同一軟體產品內,應規劃好介面部分識別符號(變數、結構、函式及常量)的命名,防止編譯、連結時產生衝突。
說明:對介面部分的識別符號應該有更嚴格限制,防止衝突。如可規定介面部分的變數與常量之前加上“模組”標識等。
3-7:用正確的反義片語命名具有互斥意義的變數或相反動作的函式等。
說明:下面是一些在軟體中常用的反義片語。
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
cut / paste up / down
示例:
int min_sum;
int max_sum;
int add_user( BYTE *user_name );
int delete_user( BYTE *user_name );
(強制)3-8:除了編譯開關/標頭檔案等特殊應用,應避免使用_EXAMPLE_TEST_之類以下劃線開始和結尾的定義。
4 可讀性
(強制)4-1:注意運算子的優先順序,並用括號明確表示式的操作順序,避免使用預設優先順序。
說明:防止閱讀程式時產生誤解,防止因預設的優先順序與設計思想不符而導致程式出錯。
示例:下列語句中的表示式
word = (high << 8) | low (1)
if ((a | b) && (a & c)) (2)
if ((a | b) < (c & d)) (3)
如果書寫為
high << 8 | low
a | b && a & c
a | b < c & d
由於
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不會出錯,但語句不易理解;
a | b < c & d = a | (b < c) & d,(3)造成了判斷條件出錯。
4-2:避免使用不易理解的數字,用有意義的標識來替代。涉及物理狀態或者含有物理意義的常量,不應直接使用數字,必須用有意義的列舉或巨集來代替。
示例:如下的程式可讀性差。
if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
... // program code
}
應改為如下形式。
#define TRUNK_IDLE 0
#define TRUNK_BUSY 1
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... // program code
}
(建議)4-3:源程式中關係較為緊密的程式碼應儘可能相鄰。
說明:便於程式閱讀和查詢。
示例:以下程式碼佈局不太合理。
rect.length = 10;
char_poi = str;
rect.width = 5;
若按如下形式書寫,可能更清晰一些。
rect.length = 10;
rect.width = 5; // 矩形的長與寬關係較密切,放在一起。
char_poi = str;
4-4:不要使用難懂的技巧性很高的語句,除非很有必要時。
說明:高技巧語句不等於高效率的程式,實際上程式的效率關鍵在於演算法。
示例:如下表達式,考慮不周就可能出問題,也較難理解。
* stat_poi ++ += 1;
* ++ stat_poi += 1;
應分別改為如下。
*stat_poi += 1;
stat_poi++; // 此二語句功能相當於“ * stat_poi ++ += 1; ”
++ stat_poi;
*stat_poi += 1; // 此二語句功能相當於“ * ++ stat_poi += 1; ”
5 變數、結構
(建議)5-1:去掉沒必要的公共變數。
說明:公共變數是增大模組間耦合的原因之一,故應減少沒必要的公共變數以降低模組間的耦合度。
5-2:仔細定義並明確公共變數的含義、作用、取值範圍及公共變數間的關係。
說明:在對變數宣告的同時,應對其含義、作用及取值範圍進行註釋說明,同時若有必要還應說明與其它變數的關係。
5-3:明確公共變數與操作此公共變數的函式或過程的關係,如訪問、修改及建立等。
說明:明確過程操作變數的關係後,將有利於程式的進一步優化、單元測試、系統聯調以及程式碼維護等。這種關係的說明可在註釋或文件中描述。
示例:在原始檔中,可按如下注釋形式說明。
RELATION System_Init Input_Rec Print_Rec Stat_Score
Student Create Modify Access Access
Score Create Modify Access Access, Modify
注:RELATION為操作關係;System_Init、Input_Rec、Print_Rec、Stat_Score為四個不同的函式;Student、Score為兩個全域性變數;Create表示建立,Modify表示修改,Access表示訪問。
其中,函式Input_Rec、Stat_Score都可修改變數Score,故此變數將引起函式間較大的耦合,並可能增加程式碼測試、維護的難度。
5-4:當向公共變數傳遞資料時,要十分小心,防止賦與不合理的值或越界等現象發生。
說明:對公共變數賦值時,若有必要應進行合法性檢查,以提高程式碼的可靠性、穩定性。
5-5:防止區域性變數與公共變數同名。
說明:若使用了較好的命名規則,那麼此問題可自動消除。
5-6:嚴禁使用未經初始化的變數作為右值。
說明:特別是在C/C++中引用未經賦值的指標,經常會引起系統崩潰。
5-7:構造僅有一個模組或函式可以修改、建立,而其餘有關模組或函式只訪問的公共變數,防止多個不同模組或函式都可以修改、建立同一公共變數的現象。
說明:降低公共變數耦合度。
5-8:使用嚴格形式定義的、可移植的資料型別,儘量不要使用與具體硬體或軟體環境關係密切的變數。
說明:使用標準的資料型別,有利於程式的移植, 如用INT32而不是int等。
示例:如下例子(在DOS下BC3.1環境中),在移植時可能產生問題。
5-9:結構的功能要單一,是針對一種事務的抽象。
說明:設計結構時應力爭使結構代表一種現實事務的抽象,而不是同時代表多種。結構中的各元素應代表同一事務的不同側面,而不應把描述沒有關係或關係很弱的不同事務的元素放到同一結構中。
示例:如下結構不太清晰、合理。
typedef struct STUDENT_STRU
{
unsigned char name[8]; /* 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[8]; /* the student teacher's name */
unisgned char
teacher_sex; /* his teacher sex */
} STUDENT;
若改為如下,可能更合理些。
typedef struct TEACHER_STRU
{
unsigned char name[8]; /* teacher name */
unisgned char sex; /* teacher sex, as follows */
/* 0 - FEMALE; 1 - MALE */
} TEACHER;
typedef struct STUDENT_STRU
{
unsigned char name[8]; /* 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;
5-10:不要設計面面俱到、非常靈活的資料結構。
說明:面面俱到、靈活的資料結構反而容易引起誤解和操作困難。
5-11:不同結構間的關係不要過於複雜。
說明:若兩個結構間關係較複雜、密切,那麼應合為一個結構。
示例:如下兩個結構的構造不合理。
typedef struct PERSON_ONE_STRU
{
unsigned char name[8];
unsigned char addr[40];
unsigned char sex;
unsigned char city[15];
} PERSON_ONE;
typedef struct PERSON_TWO_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char tel;
} PERSON_TWO;
由於兩個結構都是描述同一事物的,那麼不如合成一個結構。
typedef struct PERSON_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char sex;
unsigned char addr[40];
unsigned char city[15];
unsigned char tel;
} PERSON;
5-12:結構中元素的個數應適中。若結構中元素個數過多可考慮依據某種原則把元素組成不同的子結構,以減少原結構中元素的個數。
說明:增加結構的可理解性、可操作性和可維護性。
示例:假如認為如上的_PERSON結構元素過多,那麼可如下對之劃分。
typedef struct PERSON_BASE_INFO_STRU
{
unsigned char name[8];
unsigned char age;
unsigned char sex;
} PERSON_BASE_INFO;
typedef struct PERSON_ADDRESS_STRU
{
unsigned char addr[40];
unsigned char city[15];
unsigned char tel;
} PERSON_ADDRESS;
typedef struct PERSON_STRU
{
PERSON_BASE_INFO person_base;
PERSON_ADDRESS person_addr;
} PERSON;
5-13:仔細設計結構中元素的佈局與排列順序,使結構容易理解、節省佔用空間,並減少引起誤用現象。
說明:合理排列結構中元素順序,可節省空間並增加可理解性。
示例:如下結構中的位域排列,將佔較大空間,可讀性也稍差。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
PERSON person;
unsigned int set_flg: 1;
} EXAMPLE;
若改成如下形式,不僅可節省1位元組空間,可讀性也變好了。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
unsigned int set_flg: 1;
PERSON person ;
} EXAMPLE;
5-14:結構的設計要儘量考慮向前相容和以後的版本升級,併為某些未來可能的應用保留餘地(如預留一些空間等)。
說明:軟體向前相容的特性,是軟體產品是否成功的重要標誌之一。如果要想使產品具有較好的前向相容,那麼在產品設計之初就應為以後版本升級保留一定餘地,並且在產品升級時必須考慮前一版本的各種特性。
5-15:留心具體語言及編譯器處理不同資料型別的原則及有關細節。
說明:如在C語言中,static區域性變數將在記憶體“資料區”中生成,而非static區域性變數將在“堆疊”中生成。這些細節對程式質量的保證非常重要。
5-16:程式設計時,要注意資料型別的強制轉換。
說明:當進行資料型別強制轉換時,其資料的意義、轉換後的取值等都有可能發生變化,而這些細節若考慮不周,就很有可能留下隱患。
5-17:對編譯系統預設的資料型別轉換,也要有充分的認識。
示例:如下賦值,多數編譯器不產生告警,但值的含義還是稍有變化。
char chr;
unsigned short int exam;
chr = -1;
exam = chr; // 編譯器不產生告警,此時exam為0xFFFF。
5-18:儘量減少沒有必要的資料型別預設轉換與強制轉換。
5-19:合理地設計資料並使用自定義資料型別,避免資料間進行不必要的型別轉換。
5-20:對自定義資料型別進行恰當命名,使它成為自描述性的,以提高程式碼可讀性。注意其命名方式在同一產品中的統一。
說明:使用自定義型別,可以彌補程式語言提供型別少、資訊量不足的缺點,並能使程式清晰、簡潔。
示例:可參考如下方式宣告自定義資料型別。
下面的宣告可使資料型別的使用簡潔、明瞭。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
下面的宣告可使資料型別具有更豐富的含義。
typedef float DISTANCE;
typedef float SCORE;
(強制)5-21:當宣告用於分散式環境或不同CPU間通訊環境的資料結構時,必須考慮機器的位元組順序、使用的位域及位元組對齊等問題 。
說明:比如Intel CPU與68360 CPU,在處理位域及整數時,其在記憶體存放的“順序”正好相反。
示例:假如有如下短整數及結構。
unsigned short int exam;
typedef struct EXAM_BIT_STRU
{ /* Intel 68360 */
unsigned int A1: 1; /* bit 0 7 */
unsigned int A2: 1; /* bit 1 6 */
unsigned int A3: 1; /* bit 2 5 */
} EXAM_BIT;
如下是Intel CPU生成短整數及位域的方式。
記憶體: 0 1 2 ... (從低到高,以位元組為單位)
exam exam低位元組 exam高位元組
記憶體: 0 bit 1 bit 2 bit ... (位元組的各“位”)
EXAM_BIT A1 A2 A3
如下是68360 CPU生成短整數及位域的方式。
記憶體: 0 1 2 ... (從低到高,以位元組為單位)
exam exam高位元組 exam低位元組
記憶體: 7 bit 6 bit 5 bit ... (位元組的各“位”)
EXAM_BIT A1 A2 A3
說明:在對齊方式下,CPU的執行效率要快得多。
示例:如下圖,當一個long型數(如圖中long1)在記憶體中的位置正好與記憶體的字邊界對齊時,CPU存取這個數只需訪問一次記憶體,而當一個long型數(如圖中的long2)在記憶體中的位置跨越了字邊界時,CPU存取這個數就需要多次訪問記憶體,如i960cx訪問這樣的數需讀記憶體三次(一個BYTE、一個SHORT、一個BYTE,由CPU的微程式碼執行,對軟體透明),所有對齊方式下CPU的執行效率明顯快多了。
1 8 16 24 32
------- ------- ------- -------
| long1 | long1 | long1 | long1 |
------- ------- ------- -------
| | | | long2 |
------- ------- ------- --------
| long2 | long2 | long2 | |
------- ------- ------- --------
| ....
6 函式、過程
6-1:對所呼叫函式的錯誤返回碼要仔細、全面地處理。
6-2:明確函式功能,精確(而不是近似)地實現函式設計。
(強制)6-3:編寫可重入函式時,應注意區域性變數的使用(如編寫C/C++語言的可重入函式時,應使用auto即預設態區域性變數或暫存器變數)。
說明:編寫C/C++語言的可重入函式時,不應使用static區域性變數,否則必須經過特殊處理,才能使函式具有可重入性。
(強制)6-4:編寫可重入函式時,若使用全域性變數,則應通過關中斷、訊號量(即P、V操作)等手段對其加以保護。
說明:若對所使用的全域性變數不加以保護,則此函式就不具有可重入性,即當多個程序呼叫此函式時,很有可能使有關全域性變數變為不可知狀態。
示例:假設Exam是int型全域性變數,函式Squre_Exam返回Exam平方值。那麼如下函式不具有可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
此函式若被多個程序呼叫的話,其結果可能是未知的,因為當(**)語句剛執行完後,另外一個使用本函式的程序可能正好被啟用,那麼當新啟用的程序執行到此函式時,將使Exam賦與另一個不同的para值,所以當控制重新回到“temp = Square_Exam( )”後,計算出的temp很可能不是預想中的結果。此函式應如下改進。
unsigned int example( int para )
{
unsigned int temp;
[申請訊號量操作] // 若申請不到“訊號量”,說明另外的程序正處於
Exam = para; // 給Exam賦值並計算其平方過程中(即正在使用此
temp = Square_Exam( ); // 訊號),本程序必須等待其釋放訊號後,才可繼
[釋放訊號量操作] // 續執行。若申請到訊號,則可繼續執行,但其
// 它程序必須等待本程序釋放訊號量後,才能再使
// 用本訊號。
return temp;
}
6-5:在同一專案組應明確規定對介面函式引數的合法性檢查應由函式的呼叫者負責還是由介面函式本身負責,預設是由函式呼叫者負責。
說明:對於模組間介面函式的引數的合法性檢查這一問題,往往有兩個極端現象,即:要麼是呼叫者和被呼叫者對引數均不作合法性檢查,結果就遺漏了合法性檢查這一必要的處理過程,造成問題隱患;要麼就是呼叫者和被呼叫者均對引數進行合法性檢查,這種情況雖不會造成問題,但產生了冗餘程式碼,降低了效率。
6-6:防止將函式的引數作為工作變數。
說明:將函式的引數作為工作變數,有可能錯誤地改變引數內容,所以很危險。對必須改變的引數,最好先用區域性變數代之,最後再將該區域性變數的內容賦給該引數。
示例:下函式的實現不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0;
for (count = 0; count < num; count++)
{
*sum += data[count]; // sum成了工作變數,不太好。
}
}
若改為如下,則更好些。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count ;
int sum_temp;
sum_temp = 0;
for (count = 0; count < num; count ++)
{
sum_temp += data[count];
}
*sum = sum_temp;
}
(強制)6-7:函式的規模儘量限制在200行以內。
說明:不包括註釋和空格行。
6-8:一個函式僅完成一件功能。
6-9:為簡單功能編寫函式。
說明:雖然為僅用一兩行就可完成的功能去編函式好象沒有必要,但用函式可使功能明確化,增加程式可讀性,亦可方便維護、測試。
示例:如下語句的功能不很明顯。
value = ( a > b ) ? a : b ;
改為如下就很清晰了。
int max (int a, int b)
{
return ((a > b) ? a : b);
}
value = max (a, b);
或改為如下。
#define MAX (a, b) (((a) > (b)) ? (a) : (b))
value = MAX (a, b);
6-10:不要設計多用途面面俱到的函式。
說明:多功能集於一身的函式,很可能使函式的理解、測試、維護等變得困難。
6-11:函式的功能應該是可以預測的,也就是隻要輸入資料相同就應產生同樣的輸出。
說明:帶有內部“儲存器”的函式的功能可能是不可預測的,因為它的輸出可能取決於內部儲存器(如某標記)的狀態。這樣的函式既不易於理解又不利於測試和維護。在C/C++語言中,函式的static區域性變數是函式的內部儲存器,有可能使函式的功能不可預測,然而,當某函式的返回值為指標型別時,則必須是STATIC的區域性變數的地址作為返回值,若為AUTO類,則返回為指標。
示例:如下函式,其返回值(即功能)是不可預測的。
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;
}
6-12:儘量不要編寫依賴於其他函式內部實現的函式。
說明:此條為函式獨立性的基本要求。由於目前大部分高階語言都是結構化的,所以通過具體語言的語法要求與編譯器功能,基本就可以防止這種情況發生。但在組合語言中,由於其靈活性,很可能使函數出現這種情況。
示例:如下是在DOS下TASM的彙編程式例子。過程Print_Msg的實現依賴於Input_Msg的具體實現,這種程式是非結構化的,難以維護、修改。
... // 程式程式碼
proc Print_Msg // 過程(函式)Print_Msg
... // 程式程式碼
jmp LABEL
... // 程式程式碼
endp
proc Input_Msg // 過程(函式)Input_Msg
... // 程式程式碼
LABEL:
... // 程式程式碼
endp
6-13:避免設計多引數函式,不使用的引數從介面中去掉。
說明:目的減少函式間介面的複雜度。
(建議)6-14:非排程函式應減少或防止控制引數,儘量只使用資料引數。
說明:本建議目的是防止函式間的控制耦合。排程函式是指根據輸入的訊息型別或控制命令,來啟動相應的功能實體(即函式或過程),而本身並不完成具體功能。控制引數是指改變函式功能行為的引數,即函式要根據此引數來決定具體怎樣工作。非排程函式的控制引數增加了函式間的控制耦合,很可能使函式間的耦合度增大,並使函式的功能不唯一。
示例:如下函式構造不太合理。
int add_sub( int a, int b, unsigned char add_sub_flg )
{
if (add_sub_flg == INTEGER_ADD)
{
return (a + b);
}
else
{
return (a b);
}
}
不如分為如下兩個函式清晰。
int add( int a, int b )
{
return (a + b);
}
int sub( int a, int b )
{
return (a b);
}
6-15:檢查函式所有引數輸入的有效性。
6-16:檢查函式所有非引數輸入的有效性,如資料檔案、公共變數等。
說明:函式的輸入主要有兩種:一種是引數輸入;另一種是全域性變數、資料檔案的輸入,即非引數輸入。函式在使用輸入之前,應進行必要的檢查。
6-17:函式名應準確描述函式的功能。
6-18:使用動賓片語為執行某操作的函式命名。如果是OOP方法,可以只有動詞(名詞是物件本身)。
示例:參照如下方式命名函式。
void print_record( unsigned int rec_ind ) ;
int input_record( void ) ;
unsigned char get_current_color( void ) ;
(建議)6-19:避免使用無意義或含義不清的動詞為函式命名。
說明:避免用含義不清的動詞如process、handle等為函式命名,因為這些動詞並沒有說明要具體做什麼。
(建議)6-20:函式的返回值要清楚、明瞭,讓使用者不容易忽視錯誤情況。
說明:函式的每種出錯返回值的意義要清晰、明瞭、準確,防止使用者誤用、理解錯誤或忽視錯誤返回碼。
6-21:除非必要,最好不要把與函式返回值型別不同的變數,以編譯系統預設的轉換方式或強制的轉換方式作為返回值返回。
6-22:讓函式在呼叫點顯得易懂、容易理解。
6-23:在呼叫函式填寫引數時,應儘量減少沒有必要的預設資料型別轉換或強制資料型別轉換。
說明:因為資料型別轉換或多或少存在危險。
6-24:避免函式中不必要語句,防止程式中的垃圾程式碼。
說明:程式中的垃圾程式碼不僅佔用額外的空間,而且還常常影響程式的功能與效能,很可能給程式的測試、維護等造成不必要的麻煩。
6-25:防止把沒有關聯的語句放到一個函式中。
說明:防止函式或過程內出現隨機內聚。隨機內聚是指將沒有關聯或關聯很弱的語句放到同一個函式或過程中。隨機內聚給函式或過程的維護、測試及以後的升級等造成了不便,同時也使函式或過程的功能不明確。使用隨機內聚函式,常常容易出現在一種應用場合需要改進此函式,而另一種應用場合又不允許這種改進,從而陷入困境。
在程式設計時,經常遇到在不同函式中使用相同的程式碼,許多開發人員都願把這些程式碼提出來,並構成一個新函式。若這些程式碼關聯較大並且是完成一個功能的,那麼這種構造是合理的,否則這種構造將產生隨機內聚的函式。
示例:如下函式就是一種隨機內聚。
void Init_Var( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的長與寬 */
Point.x = 10;
Point.y = 10; /* 初始化“點”的座標 */
}
矩形的長、寬與點的座標基本沒有任何關係,故以上函式是隨機內聚。
應如下分為兩個函式:
void Init_Rect( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的長與寬 */
}
void Init_Point( void )
{
Point.x = 10;
Point.y = 10; /* 初始化“點”的座標 */
}
6-26:如果多段程式碼重複做同一件事情,那麼在函式的劃分上可能存在問題。
說明:若此段程式碼各語句之間有實質性關聯並且是完成同一件功能的,那麼可考慮把此段程式碼構造成一個新的函式。
6-27:功能不明確較小的函式,特別是僅有一個上級函式呼叫它時,應考慮把它合併到上級函式中,而不必單獨存在。
說明:模組中函式劃分的過多,一般會使函式間的介面變得複雜。所以過小的函式,特別是扇入很低的或功能不明確的函式,不值得單獨存在。
6-28:設計高扇入、合理扇出(小於7)的函式。
說明:扇出是指一個函式直接呼叫(控制)其它函式的數目,而扇入是指有多少上級函式呼叫它。
扇出過大,表明函式過分複雜,需要控制和協調過多的下級函式;而扇出過小,如總是1,表明函式的呼叫層次可能過多,這樣不利程式閱讀和函式結構的分析,並且程式執行時會對系統資源如堆疊空間等造成壓力。函式較合理的扇出(排程函式除外)通常是3-5。扇出太大,一般是由於缺乏中間層次,可適當增加中間層次的函式。扇出太小,可把下級函式進一步分解多個函式,或合併到上級函式中。當然分解或合併函式時,不能改變要實現的功能,也不能違背函式間的獨立性。
扇入越大,表明使用此函式的上級函式越多,這樣的函式使用效率高,但不能違背函式間的獨立性而單純地追求高扇入。公共模組中的函式及底層函式應該有較高的扇入。
較良好的軟體結構通常是頂層函式的扇出較高,中層函式的扇出較少,而底層函式則扇入到公共模組中。
(強制)6-29:除非為某些演算法或功能的實現方便, 否則禁用遞迴呼叫。
說明:遞迴呼叫特別是函式間的遞迴呼叫(如A->B->C->A),影響程式的可理解性;遞迴呼叫一般都佔用較多的系統資源(如棧空間);遞迴呼叫對程式的測試有一定影響。故除非為某些演算法或功能的實現方便,應減少沒必要的遞迴呼叫。
6-30:仔細分析模組的功能及效能需求,並進一步細分,同時若有必要畫出有關資料流圖,據此來進行模組的函式劃分與組織。
說明:函式的劃分與組織是模組的實現過程中很關鍵的步驟,如何劃分出合理的函式結構,關係到模組的最終效率和可維護性、可測性等。根據模組的功能圖或/及資料流圖映射出函式結構是常用方法之一。
6-31:改進模組中函式的結構,降低函式間的耦合度,並提高函式的獨立性以及程式碼可讀性、效率和可維護性。優化函式結構時,要遵守以下原則:
(1)不能影響模組功能的實現。
(2)仔細考查模組或函數出錯處理及模組的效能要求並進行完善。
(3)通過分解或合併函式來改進軟體結構。
(4)考查函式的規模,過大的要進行分解。
(5)降低函式間介面的複雜度。
(6)不同層次的函式呼叫要有較合理的扇入、扇出。
(7)函式功能應可預測。
(8)提高函式內聚。(單一功能的函式內聚最高)
說明:對初步劃分後的函式結構應進行改進、優化,使之更為合理。
(強制)6-32:在多工作業系統的環境下程式設計,要注意函式可重入性的構造。
說明:可重入性是指函式可以被多個任務程序呼叫。在多工作業系統中,函式是否具有可重入性是非常重要的,因為這是多個程序可以共用此函式的必要條件。另外,編譯器是否提供可重入函式庫,與它所服務的作業系統有關,只有作業系統是多工時,編譯器才有可能提供可重入函式庫。如DOS下BC和MSC等就不具備可重入函式庫,因為DOS是單使用者單任務作業系統。
(建議)6-33:避免使用BOOL引數。
說明:原因有二,其一是BOOL引數值無意義,TURE/FALSE的含義是非常模糊的,在呼叫時很難知道該引數到底傳達的是什麼意思;其二是BOOL引數值不利於擴充。還有NULL也是一個無意義的單詞。
6-34:對於提供了返回值的函式,在引用時最好使用其返回值。
(建議)6-35:當一個過程(函式)中對較長變數(一般是結構的成員)有較多引用時,可以用一個意義相當的巨集代替。
說明:這樣可以增加程式設計效率和程式的可讀性。
示例:在某過程中較多引用TheReceiveBuffer[FirstSocket].byDataPtr,
則可以通過以下巨集定義來代替:
# define pSOCKDATA TheReceiveBuffer[FirstScoket].byDataPtr
7 可測性
7-1:在同一專案組或產品組內,要有一套統一的為整合測試與系統聯調準備的調測開關及相應列印函式,並且要有詳細的說明。
說明:本規則是針對專案組或產品組的。
7-2:在同一專案組或產品組內,調測打印出的資訊串的格式要有統一的形式。資訊串中至少要有所在模組名(或原始檔名)及行號。
說明:統一的調測資訊格式便於整合測試。
7-3:程式設計的同時要為單元測試選擇恰當的測試點,並仔細構造測試程式碼、測試用例,同時給出明確的註釋說明。測試程式碼部分應作為(模組中的)一個子模組,以方便測試程式碼在模組中的安裝與拆卸(通過調測開關)。
說明:為單元測試而準備。
7-4:在進行整合測試/系統聯調之前,要構造好測試環境、測試專案及測試用例,同時仔細分析並優化測試用例,以提高測試效率。
說明:好的測試用例應儘可能模擬出程式所遇到的邊界值、各種複雜環境及一些極端情況等。
7-5:使用斷言來發現軟體問題,提高程式碼可測性。
說明:斷言是對某種假設條件進行檢查(可理解為若條件成立則無動作,否則應報告),它可以快速發現並定位軟體問題,同時對系統錯誤進行自動報警。斷言可以對在系統中隱藏很深,用其它手段極難發現的問題進行定位,從而縮短軟體問題定位時間,提高系統的可測性。實際應用時,可根據具體情況靈活地設計斷言。
示例:下面是C語言中的一個斷言,用巨集來設計的。(其中NULL為0L)
#ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
void exam_assert( char * file_name, unsigned int line_no )
{
printf( "/n[EXAM]Assert failed: %s, line %u/n",
file_name, line_no );
abort( );
}
#define EXAM_ASSERT( condition )
if (condition) // 若條件成立,則無動作
NULL;
else // 否則報告
exam_assert( __FILE__, __LINE__ )
#else // 若不使用斷言測試
#define EXAM_ASSERT(condition) NULL
#endif /* end of ASSERT */
7-6:用斷言來檢查程式正常執行時不應發生但在調測時有可能發生的非法情況。
7-7:不能用斷言來檢查最終產品肯定會出現且必須處理的錯誤情況。
說明:斷言是用來處理不應該發生的錯誤情況的,對於可能會發生的且必須處理的情況要寫防錯程式,而不是斷言。如某模組收到其它模組或鏈路上的訊息後,要對訊息的合理性進行檢查,此過程為正常的錯誤檢查,不能用斷言來實現。
7-8:對較複雜的斷言加上明確的註釋。
說明:為複雜的斷言加註釋,可澄清斷言含義並減少不必要的誤用。
7-9:用斷言確認函式的引數。
示例:假設某函式引數中有一個指標,那麼使用指標前可對它檢查,如下。
int exam_fun( unsigned char *str )
{
EXAM_ASSERT( str != NULL ); // 用斷言檢查“假設指標不為空”這個條件
... //other program code
}
7-10:用斷言保證沒有定義的特性或功能不被使用。
示例:假設某通訊模組在設計時,準備提供“無連線”和“連線” 這兩種業務。但當前的版本中僅實現了“無連線”業務,且在此版本的正式發行版中,使用者(上層模組)不應產生“連線”業務的請求,那麼在測試時可用斷言檢查使用者是否使用“連線”業務。如下。
#define EXAM_CONNECTIONLESS 0 // 無連線業務
#define EXAM_CONNECTION 1 // 連線業務
int msg_process( EXAM_MESSAGE *msg )
{
unsigned char service; /* message service class */
EXAM_ASSERT( msg != NULL );
service = get_msg_service_class( msg );
EXAM_ASSERT( service != EXAM_CONNECTION ); // 假設不使用連線業務
... //other program code
}
(建議)7-11:用斷言對程式開發環境(OS/Compiler/Hardware)的假設進行檢查。
說明:程式執行時所需的軟硬體環境及配置要求,不能用斷言來檢查,而必須由一段專門程式碼處理。用斷言僅可對程式開發環境中的假設及所配置的某版本軟硬體是否具有某種功能的假設進行檢查。如某網絡卡是否在系統執行環境中配置了,應由程式中正式程式碼來檢查;而此網絡卡是否具有某設想的功能,則可由斷言來檢查。
對編譯器提供的功能及特性假設可用斷言檢查,原因是軟體最終產品(即執行程式碼或機器碼)與編譯器已沒有任何直接關係,即軟體執行過程中(注意不是編譯過程中)不會也不應該對編譯器的功能提出任何需求。
示例:用斷言檢查編譯器的int型資料佔用的記憶體空間是否為2,如下。
EXAM_ASSERT( sizeof( int ) == 2 );
(強制)7-12:正式軟體產品中應把斷言及其它調測程式碼去掉(即把有關的調測開關關掉)。
說明:加快軟體執行速度。
(強制)7-13:在軟體系統中設定與取消有關測試手段,不能對軟體實現的功能等產生影響。
說明:即有測試程式碼的軟體和關掉測試程式碼的軟體,在功能行為上應一致。
7-14:用調測開關來切換軟體的DEBUG版和正式版,而不要同時存在正式版本和DEBUG版本的不同原始檔,以減少維護的難度。
7-15:軟體的DEBUG版本和發行版本應該統一維護,不允許分家,並且要時刻注意保證兩個版本在實現功能上的一致性。
7-16:在編寫程式碼之前,應預先設計好程式除錯與測試的方法和手段,並設計好各種調測開關及相應測試程式碼如列印函式等。
說明:程式的除錯與測試是軟體生存週期中很重要的一個階段,如何對軟體進行較全面、高率的測試並儘可能地找出軟體中的錯誤就成為很關鍵的問題。因此在編寫原始碼之前,除了要有一套比較完善的測試計劃外,還應設計出一系列程式碼測試手段,為單元測試、整合測試及系統聯調提供方便。
7-17:調測開關應分為不同級別和型別。
說明:調測開關的設定及分類應從以下幾方面考慮:針對模組或系統某部分程式碼的調測;針對模組或系統某功能的調測;出於某種其它目的,如對效能、容量等的測試。這樣做便於軟體功能的調測,並且便於模組的單元測試、系統聯調等。
(建議)7-18:編寫防錯程式,然後在處理錯誤之後可用斷言宣佈發生錯誤。
示例:假如某模組收到通訊鏈路上的訊息,則應對訊息的合法性進行檢查,若訊息類別不是通訊協議中規定的,則應進行出錯處理,之後可用斷言報告,如下例。
#ifdef _EXAM_ASSERT_TEST_ // 若使用斷言測試
/* Notice: this function does not call 'abort' to exit program */
void assert_report( char * file_name, unsigned int line_no )
{
printf( "/n[EXAM]Error Report: %s, line %u/n",
file_name, line_no );
}
#define ASSERT_REPORT( condition )
if ( condition ) // 若條件成立,則無動作
NULL;
else // 否則報告
assert_report ( __FILE__, __LINE__ )
#else // 若不使用斷言測試
#define ASSERT_REPORT( condition ) NULL
#endif /* end of ASSERT */
int msg_handle( unsigned char msg_name, unsigned char * msg )
{
switch( msg_name )
{
case MSG_ONE:
... // 訊息MSG_ONE處理
return MSG_HANDLE_SUCCESS;
... // 其它合法訊息處理
default:
... // 訊息出錯處理
ASSERT_REPORT( FALSE ); // “合法”訊息不成立,報告
return MSG_HANDLE_ERROR;
}
}
8 程式效率
8-1:程式設計時要經常注意程式碼的效率。
說明:程式碼效率分為全域性效率、區域性效率、時間效率及空間效率。全域性效率是站在整個系統的角度上的系統效率;區域性效率是站在模組或函式角度上的效率;時間效率是程式處理輸入任務所需的時間長短;空間效率是程式所需記憶體空間,如機器程式碼空間大小、資料空間大小、棧空間大小等。
8-2:在保證軟體系統的正確性、穩定性、可讀性及可測性的前提下,提高程式碼效率。
說明:不能一味地追求程式碼效率,而對軟體的正確性、穩定性、可讀性及可測性造成影響。
(強制)8-3:區域性效率應為全域性效率服務,不能因為提高區