目錄
1 概述 5
1.1 編寫目的 5
1.2 文檔約定 5
1.3 預期的讀者和閱讀建議 5
1.4 參考文獻 5
2 排版要求 5
2.1 程序塊縮進 5
2.2 程序塊之間空行 5
2.3 長語句和長表達式 6
2.4 循環、判斷等長表達式或語句 7
2.5 長參數 7
2.6 短語句 8
2.7 條件、循環語句 8
2.8 語句對齊 8
2.9 函數、過程和結構等語句塊 9
2.10 程序塊分界符 9
2.11 操做符先後空格 10
2.12 其餘 11
3 註釋 11
3.1 有效註釋量 11
3.2 公司標識 11
3.3 說明性文件 12
3.4 源文件頭 13
3.5 函數頭部說明 13
3.6 註釋與代碼一致 14
3.7 註釋內容 14
3.8 註釋縮寫 14
3.9 註釋位置 14
3.10 變量、常量註釋 15
3.11 數據結構的註釋 15
3.12 全局變量 16
3.13 註釋縮排 16
3.14 註釋與代碼之間空行 17
3.15 變量定義、分支語句 17
3.16 其餘 19
4 標識符命名 20
4.1 命名清晰 20
4.2 特殊命名需註釋 21
4.3 命名風格保持一致 21
4.4 變量命名 21
4.5 命名規範與系統風格一致 21
4.6 其餘 22
5 可讀性 23
5.1 運算符優先級 23
5.2 避免直接使用數字做爲標識符 23
5.3 其餘 24
6 變量、結構 25
6.1 公共變量 25
6.2 公共變量說明 25
6.3 公共變量訪問說明 25
6.4 公共變量賦值 26
6.5 防止局部變量與公共變量同名。 26
6.6 嚴禁使用未經初始化的變量做爲右值。 26
6.7 其餘 26
7 函數、過程 34
7.1 對所調用函數的錯誤返回碼要仔細、全面地處理。 34
7.2 明確函數功能,精確(而不是近似)地實現函數設計。 34
7.3 局部變量 34
7.4 全局變量 34
7.5 接口函數參數 35
7.6 其餘 35
8 可測性 44
8.1 調測開關 44
8.2 打印信息 45
8.3 單元測試 45
8.4 集成測試 45
8.5 斷言使用 45
8.6 設置與取消有關測試手段時,不能影響軟件功能功能 48
8.7 版本維護 48
8.8 其餘 48
9 程序效率 50
9.1 編程時要常常注意代碼的效率。 50
9.2 提升代碼效率 50
9.3 全局效率高於局部效率 51
9.4 提升代碼空間效率 51
9.5 循環體內工做量最小化 52
9.6 其餘 53
10 質量保證 56
10.1 在軟件設計過程當中構築軟件質量。 56
10.2 代碼質量保證優先原則 56
10.3 只引用屬於本身的存貯空間。 56
10.4 防止引用已經釋放的內存空間。 56
10.5 內存及時釋放 57
10.6 文件句柄及時關閉 57
10.7 防止內存操做越界 58
10.8 認真處理程序所能遇到的各類出錯狀況 59
10.9 初始化變量 59
10.10 數據一致性檢查 59
10.11 嚴禁隨意更改其它模塊或系統的有關設置和配置 59
10.12 不能隨意改變與其它模塊的接口 59
10.13 系統接口 59
10.14 編程時,要防止差1錯誤 61
10.15 操做符檢查 61
10.16 分支語句寫完整 62
10.17 使用return語句 62
10.18 不要濫用goto語句 62
10.19 其餘 62
11 代碼編輯、編譯、審查 65
11.1 打開編譯器的全部告警開關對程序進行編譯 65
11.2 在產品軟件(項目組)中,要統一編譯開關選項 65
11.3 經過代碼走讀及審查方式對代碼進行檢查。 65
11.4 測試部測試產品以前,應對代碼進行抽查及評審 65
11.5 其餘 65
12 代碼測試、維護 67
12.1 單元測試要求至少達到語句覆蓋 67
12.2 單元測試開始要跟蹤每一條語句,並觀察數據流及變量的變化 67
12.3 清理、整理或優化後的代碼要通過審查及測試。 67
12.4 代碼版本升級要通過嚴格測試 67
12.5 使用工具軟件對代碼版本進行維護 67
12.6 正式版本上軟件的任何修改都應有詳細的文檔記錄 67
12.7 其餘 67
13 宏 68
13.1 用宏定義表達式時,要使用完備的括號 68
13.2 將宏所定義的多條表達式放在大括號中 68
13.3 使用宏時,不容許參數發生變化 69算法
1 概述
1.1 編寫目的
爲規範軟件開發人員的代碼編寫提供參考依據和統一標準。
1.2 文檔約定
說明本文檔中所用到的專用術語定義或解釋,縮略詞定義。
1.3 預期的讀者和閱讀建議
本文檔適用於全部軟件開發人員。
1.4 參考文獻
列出有關的參考文件,如:
a.屬於本項目的其餘已發表文件;
b.本文件中各處引用的文檔資料。
列出這些文件的標題、做者,說明可以獲得這些文件資料的來源。
2 排版要求
2.1 程序塊縮進
程序塊要採用縮進風格編寫,縮進的空格數爲4個。
說明:對於由開發工具自動生成的代碼能夠有不一致。
2.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;
2.3 長語句和長表達式
較長的語句(>80字符)要分紅多行書寫,長表達式要在低優先級操做符處劃分新行,操做符放在新行之首,劃分出的新行要進行適當的縮進,使排版整齊,語句可讀。
示例:
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 );markdown
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
2.4 循環、判斷等長表達式或語句
循環、判斷等語句中如有較長的表達式或語句,則要進行適應的劃分,長表達式要在低優先級操做符處劃分新行,操做符放在新行之首。
示例:
if ((taskno < max_act_task_number)
&& (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
}
2.5 長參數
若函數或過程當中的參數較長,則要進行適當的劃分。
示例:
n7stat_str_compare((BYTE *) & stat_object,
(BYTE *) & (act_task_table[taskno].stat_object),
sizeof (_STAT_OBJECT));less
n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER
+ index, stat_object );
2.6 短語句
不容許把多個短語句寫在一行中,即一行只寫一條語句。
示例:以下例子不符合規範。
rect.length = 0; rect.width = 0;編程語言
應以下書寫
rect.length = 0;
rect.width = 0;
2.7 條件、循環語句
if、for、do、while、case、switch、default等語句自佔一行,且if、for、do、while等語句的執行語句部分不管多少都要加括號{}。
示例:以下例子不符合規範。
if (pUserCR == NULL) return;
應以下書寫:
if (pUserCR == NULL)
{
return;
}
2.8 語句對齊
對齊只使用空格鍵,不使用TAB鍵。
說明:以避免用不一樣的編輯器閱讀程序時,因TAB鍵所設置的空格數目不一樣而形成程序佈局不整齊,
不要使用BC做爲編輯器合版本,由於BC會自動將8個空格變爲一個TAB鍵,所以使用BC合入的版本大多會將縮進變亂。
2.9 函數、過程和結構等語句塊
函數或過程的開始、結構的定義及循環、判斷等語句中的代碼都要採用縮進風格,case語句下的狀況處理語句也要聽從語句縮進要求。
2.10 程序塊分界符
程序塊的分界符(如C/C++語言的大括號‘{’和‘}’)應各獨佔一行而且位於同一列,同時與引用它們的語句左對齊。在函數體的開始、類的定義、結構的定義、枚舉的定義以及if、for、do、while、switch、case語句中的程序都要採用如上的縮進方式。
示例:以下例子不符合規範。
for (…) {
… // program code
}
if (…)
{
… // program code
}
void example_fun( void )
{
… // program code
}
應以下書寫。
for (…)
{
… // program code
}
if (…)
{
… // program code
}
void example_fun( void )
{
… // program code
}
2.11 操做符先後空格
在兩個以上的關鍵字、變量、常量進行對等操做時,它們之間的操做符以前、以後或者先後要加空格;進行非對等操做時,若是是關係密切的當即操做符(如->),後不該加空格。
說明:採用這種鬆散方式編寫代碼的目的是使代碼更加清晰。
因爲留空格所產生的清晰性是相對的,因此,在已經很是清晰的語句中沒有必要再留空格,若是語句已足夠清晰則括號內側(即左括號後面和右括號前面)不須要加空格,多重括號間沒必要加空格,由於在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)
2.12 其餘
2.12.1 一行程序以小於80字符爲宜,不要寫得過長。
3 註釋
3.1 有效註釋量
通常狀況下,源程序有效註釋量必須在20%以上。
說明:註釋的原則是有助於對程序的閱讀理解,在該加的地方都加了,註釋不宜太多也不
能太少,註釋語言必須準確、易懂、簡潔。
3.2 公司標識
在頭文件中加入公司標識。
示例以下:
****************************************************************/
/* */
/* Copyright (c) 1996-1998 XXXXXX Company */
/* xxxxxxxx 版權全部 1996-1998 */
/* */
/* PROPRIETARY RIGHTS of XXXXXX Company are involved in the */
/* subject matter of this material. All manufacturing, reproduction, use,*/
/* and sales rights pertaining to this subject matter are governed by the */
/* license agreement. The recipient of this software implicitly accepts */
/* the terms of the license. */
/* 本軟件文檔資料是xxx公司的資產,任何人士閱讀和使用本資料必須得到 */
/* 相應的書面受權,承擔保密責任和接受相應的法律約束. */
/* */
/****************************************************************/
3.3 說明性文件
說明性文件(如頭文件.h文件、.inc文件、.def文件、編譯說明文件.cfg等)頭部應進行註釋,註釋必須列出:版權說明、版本號、生成日期、做者、內容、功能、與其它文件的關係、修改日誌等,頭文件的註釋中還應有函數功能簡要說明。
示例:下面這段頭文件的頭註釋比較標準,固然,並不侷限於此格式,但上述信息建議要包含在內。
/***************************************
Copyright (C), 1996-1998, xxxxx. Co., Ltd.
File name: // 文件名
Author: Version: Date: // 做者、版本及完成日期
Description: // 用於詳細說明此程序文件完成的主要功能,與其餘模塊
// 或函數的接口,輸出值、取值範圍、含義及參數間的控
// 制、順序、獨立或依賴等關係
Others: // 其它內容的說明
Function List: // 主要函數列表,每條記錄應包括函數名及功能簡要說明
1. ….
History: // 修改歷史記錄列表,每條修改記錄應包括修改日期、修改
// 者及修改內容簡述
1. Date:
Author:
Modification:
2. …
***************************************/
3.4 源文件頭
源文件頭部應進行註釋,列出:版權說明、版本號、生成日期、做者、模塊目的/功能、主要函數及其功能、修改日誌等。
示例:下面這段源文件的頭註釋比較標準,固然,並不侷限於此格式,但上述信息建議要包含在內。
/**************************************************
Copyright (C), 1988-1999, Xxxxxx Tech. Co., Ltd.
FileName: test.cpp
Author: Version : Date:
Description: // 模塊描述
Version: // 版本信息
Function List: // 主要函數及其功能
1. ——-
History: // 歷史修改記錄
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;
3.10 變量、常量註釋
對於全部有物理含義的變量、常量,若是其命名不是充分自注釋的,在聲明時都必須加以註釋,說明其物理含義。變量、常量、宏的註釋應放在其上方相鄰位置或右方。
示例:
/* active statistic task number */
3.11 數據結構的註釋
數據結構聲明(包括數組、結構、類、枚舉等),若是其命名不是充分自注釋的,必須加以註釋。對數據結構的註釋應放在其上方相鄰位置,不可放在下面;對結構中的每一個域的註釋放在此域的右方。
示例:可按以下形式說明枚舉/數據/聯合結構。
/* 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*/
};
3.12 全局變量
全局變量要有較詳細的註釋,包括對其功能、取值範圍、哪些函數或過程存取它以及存取時注意事項等的說明。
示例:
/* 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;
3.13 註釋縮排
註釋與所描述內容進行一樣的縮排。
說明:可以使程序排版整齊,並方便註釋的閱讀與理解。
示例:以下例子,排版不整齊,閱讀稍感不方便。
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
}
3.14 註釋與代碼之間空行
將註釋與其上面的代碼用空行隔開。
示例:以下例子,顯得代碼過於緊湊。
/* code one comments */
program code one
/* code two comments */
program code two
應以下書寫
/* code one comments */
program code one
/* code two comments */
program code two
3.15 變量定義、分支語句
對變量的定義和分支語句(條件分支、循環語句等)必須編寫註釋。
說明:這些語句每每是程序實現某一特定功能的關鍵,對於維護人員來講,良好的註釋幫助更好的理解程序,有時甚至優於看設計文檔。
示例(注意斜體加粗部分):
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;
…
3.16 其餘
3.16.1 避免在一行代碼或表達式的中間插入註釋。
說明:除非必要,不該在代碼或表達中間插入註釋,不然容易使代碼可理解性變差。
3.16.2 經過對函數或過程、變量、結構等正確的命名以及合理地組織代碼的結構,使代碼成爲自注釋的。
說明:清晰準確的函數、變量等的命名,可增長代碼可讀性,並減小沒必要要的註釋。
3.16.3 在代碼的功能、意圖層次上進行註釋,提供有用、額外的信息。
說明:註釋的目的是解釋代碼的目的、功能和採用的方法,提供代碼之外的信息,幫助讀者理解代碼,防止不必的重複註釋信息。
示例:以下注釋意義不大。
/* if receive_flag is TRUE */
if (receive_flag)
而以下的註釋則給出了額外有用的信息。
/* if mtp receive a message from links */
if (receive_flag)
3.16.4 在程序塊的結束行右方加註釋標記,以代表某程序塊的結束。
說明:當代碼段較長,特別是多重嵌套時,這樣作可使代碼更清晰,更便於閱讀。
示例:參見以下例子。
if (…)
{
// program code
while (index < MAX_INDEX) { // program code } /* end of while (index < MAX_INDEX) */ // 指明該條while語句結束
} /* end of if (…)*/ // 指明是哪條if語句結束
3.16.5 註釋格式儘可能統一,建議使用「/* …… */」。
3.16.6 註釋應考慮程序易讀及外觀排版的因素,使用的語言如果中、英兼有的,建議多使用中文,除非能用很是流利準確的英文表達。
說明:註釋語言不統一,影響程序易讀性和外觀排版,出於對維護人員的考慮,建議使用
中文。
4 標識符命名
4.1 命名清晰
標識符的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或你們基本能夠理解的縮寫,避免令人產生誤解。
說明:較短的單詞可經過去掉「元音」造成縮寫;較長的單詞可取單詞的頭幾個字母造成
縮寫;一些單詞有你們公認的縮寫。
示例:以下單詞的縮寫可以被你們基本承認。
temp 可縮寫爲 tmp ;
flag 可縮寫爲 flg ;
statistic 可縮寫爲 stat ;
increment 可縮寫爲 inc ;
message 可縮寫爲 msg ;
4.2 特殊命名需註釋
命名中若使用特殊約定或縮寫,則要有註釋說明。
說明:應該在源文件的開始之處,對文件中所使用的縮寫或約定,特別是特殊的縮寫,進
行必要的註釋說明。
4.3 命名風格保持一致
本身特有的命名風格,要自始至終保持一致,不可來回變化。
說明:我的的命名風格,在符合所在項目組或產品組的命名規則的前提下,纔可以使用。(即
命名規則中沒有規定到的地方纔可有我的命名風格)。
4.4 變量命名
對於變量命名,禁止取單個字符(如i、j、k…),建議除了要有具體含義外,還能代表其變量類型、數據類型等,但i、j、k做局部循環變量是容許的。
說明:變量,尤爲是局部變量,若是用單個字符表示,很容易敲錯(如i寫成j),而編譯時又檢查不出來,有可能爲了這個小小的錯誤而花費大量的查錯時間。
示例:下面所示的局部變量名的定義方法能夠借鑑。
int liv_Width
其變量名解釋以下:
l 局部變量(Local) (其它:g 全局變量(Global)…)
i 數據類型(Interger)
v 變量(Variable) (其它:c 常量(Const)…)
Width 變量含義
這樣能夠防止局部變量與全局變量重名。
4.5 命名規範與系統風格一致
命名規範必須與所使用的系統風格保持一致,並在同一項目中統一,好比採用UNIX的全小寫加下劃線的風格或大小寫混排的方式,不要使用大小寫與下劃線混排的方式,用做特殊標識如標識成員變量或全局變量的m_和g_,其後加上大小寫混排的方式是容許的。
示例: Add_User不容許,add_user、AddUser、m_AddUser容許。
4.6 其餘
4.6.1 除非必要,不要用數字或較奇怪的字符來定義標識符。
示例:以下命名,令人產生疑惑。
void set_sls00( BYTE sls );
應改成有意義的單詞命名
void set_udt_msg_sls( BYTE sls );
4.6.2 在同一軟件產品內,應規劃好接口部分標識符(變量、結構、函數及常量)的命名,防止編譯、連接時產生衝突。
說明:對接口部分的標識符應該有更嚴格限制,防止衝突。如可規定接口部分的變量與常量以前加上「模塊」標識等。
4.6.3 用正確的反義詞組命名具備互斥意義的變量或相反動做的函數等。
說明:下面是一些在軟件中經常使用的反義詞組。
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.6.4 除了編譯開關/頭文件等特殊應用,應避免使用EXAMPLE_TEST之類如下劃線開始和結尾的定義。
5 可讀性
5.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)形成了判斷條件出錯。
5.2 避免直接使用數字做爲標識符
避免使用不易理解的數字,用有意義的標識來替代。涉及物理狀態或者含有物理意義的常量,不該直接使用數字,必須用有意義的枚舉或宏來代替。
示例:以下的程序可讀性差。
if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
… // program code
}
應改成以下形式。
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
… // program code
}
5.3 其餘
5.3.1 源程序中關係較爲緊密的代碼應儘量相鄰。
說明:便於程序閱讀和查找。
示例:如下代碼佈局不太合理。
rect.length = 10;
char_poi = str;
rect.width = 5;
若按以下形式書寫,可能更清晰一些。
rect.length = 10;
rect.width = 5; // 矩形的長與寬關係較密切,放在一塊兒。
char_poi = str;
5.3.2 不要使用難懂的技巧性很高的語句,除非頗有必要時。
說明:高技巧語句不等於高效率的程序,實際上程序的效率關鍵在於算法。
示例:以下表達式,考慮不周就可能出問題,也較難理解。
* stat_poi ++ += 1;
應分別改成以下。
*stat_poi += 1;
stat_poi++; // 此二語句功能至關於「 * stat_poi ++ += 1; 」
++ stat_poi;
stat_poi += 1; // 此二語句功能至關於「 ++ stat_poi += 1;
6 變量、結構
6.1 公共變量
去掉不必的公共變量。
說明:公共變量是增大模塊間耦合的緣由之一,故應減小不必的公共變量以下降模塊間
的耦合度。
6.2 公共變量說明
仔細定義並明確公共變量的含義、做用、取值範圍及公共變量間的關係。
說明:在對變量聲明的同時,應對其含義、做用及取值範圍進行註釋說明,同時如有必要
還應說明與其它變量的關係。
6.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,故此變量將引發函數間較大的耦合,並可能增長代碼測試、維護的難度。
6.4 公共變量賦值
當向公共變量傳遞數據時,要十分當心,防止賦與不合理的值或越界等現象發生。
說明:對公共變量賦值時,如有必要應進行合法性檢查,以提升代碼的可靠性、穩定性。
6.5 防止局部變量與公共變量同名。
說明:若使用了較好的命名規則,那麼此問題可自動消除。
6.6 嚴禁使用未經初始化的變量做爲右值。
說明:特別是在C/C++中引用未經賦值的指針,常常會引發系統崩潰。
6.7 其餘
6.7.1 防止多個模塊共用公共變量
構造僅有一個模塊或函數能夠修改、建立,而其他有關模塊或函數只訪問的公共變量,防止多個不一樣模塊或函數均可以修改、建立同一公共變量的現象。
說明:下降公共變量耦合度。
6.7.2 數據類型
使用嚴格形式定義的、可移植的數據類型,儘可能不要使用與具體硬件或軟件環境關係密切的變量。
說明:使用標準的數據類型,有利於程序的移植。
示例:以下例子(在DOS下BC3.1環境中),在移植時可能產生問題。
void main()
{
register int index; // 寄存器變量
_AX = 0x4000; // _AX是BC3.1提供的寄存器「僞變量」 ... // program code
}
6.7.3 結構
結構的功能要單一,是針對一種事務的抽象。
說明:設計結構時應力爭使結構表明一種現實事務的抽象,而不是同時表明多種。結構中
的各元素應表明同一事務的不一樣側面,而不該把描述沒有關係或關係很弱的不一樣事務的元素放到同一結構中。
示例:以下結構不太清晰、合理。
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;
6.7.4 不要設計面面俱到、很是靈活的數據結構
說明:面面俱到、靈活的數據結構反而容易引發誤解和操做困難。
6.7.5 不一樣結構間的關係不要過於複雜
說明:若兩個結構間關係較複雜、密切,那麼應合爲一個結構。
示例:以下兩個結構的構造不合理。
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;
6.7.6 結構中元素的個數應適中
若結構中元素個數過多可考慮依據某種原則把元素組成不一樣的子結構,以減小原結構中元素的個數。
說明:增長結構的可理解性、可操做性和可維護性。
示例:假如認爲如上的_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;
6.7.7 結構中元素佈局和排列
仔細設計結構中元素的佈局與排列順序,使結構容易理解、節省佔用空間,並減小引發誤用現象。
說明:合理排列結構中元素順序,可節省空間並增長可理解性。
示例:以下結構中的位域排列,將佔較大空間,可讀性也稍差。
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;
6.7.8 結構中的保留項
結構的設計要儘可能考慮向前兼容和之後的版本升級,併爲某些將來可能的應用保留餘地(如預留一些空間等)。
說明:軟件向前兼容的特性,是軟件產品是否成功的重要標誌之一。若是要想使產品具備
較好的前向兼容,那麼在產品設計之初就應爲之後版本升級保留必定餘地,而且在產品升級時必須考慮前一版本的各類特性。
6.7.9 留心具體語言及編譯器處理不一樣數據類型的原則及有關細節。
說明:如在C語言中,static局部變量將在內存「數據區」中生成,而非static局部
變量將在「堆棧」中生成。這些細節對程序質量的保證很是重要。
6.7.10 編程時,要注意數據類型的強制轉換。
說明:當進行數據類型強制轉換時,其數據的意義、轉換後的取值等都有可能發生變化,
而這些細節若考慮不周,就頗有可能留下隱患。
6.7.11 對編譯系統默認的數據類型轉換,也要有充分的認識。
示例:以下賦值,多數編譯器不產生告警,但值的含義仍是稍有變化。
char chr;
unsigned short int exam;
chr = -1;
exam = chr; // 編譯器不產生告警,此時exam爲0xFFFF。
6.7.12 儘可能減小沒有必要的數據類型默認轉換與強制轉換。
6.7.13 合理地設計數據並使用自定義數據類型,避免數據間進行沒必要要的類型轉換。
6.7.14 對自定義數據類型進行恰當命名,使它成爲自描述性的,以提升代碼可讀性。注意其命名方式在同一產品中的統一。
說明:使用自定義類型,能夠彌補編程語言提供類型少、信息量不足的缺點,並能使程序
清晰、簡潔。
示例:可參考以下方式聲明自定義數據類型。
下面的聲明可以使數據類型的使用簡潔、明瞭。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
下面的聲明可以使數據類型具備更豐富的含義。
typedef float DISTANCE;
typedef float SCORE;
6.7.15 當聲明用於分佈式環境或不一樣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 | |
——- ——- ——- ——–
| ….
7 函數、過程
7.1 對所調用函數的錯誤返回碼要仔細、全面地處理。
7.2 明確函數功能,精確(而不是近似)地實現函數設計。
7.3 局部變量
編寫可重入函數時,應注意局部變量的使用(如編寫C/C++語言的可重入函數時,應使用auto即缺省態局部變量或寄存器變量)。
說明:編寫C/C++語言的可重入函數時,不該使用static局部變量,不然必須通過特殊
處理,才能使函數具備可重入性。
7.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;
}
7.5 接口函數參數
在同一項目組應明確規定對接口函數參數的合法性檢查應由函數的調用者負責仍是由接口函數自己負責,缺省是由函數調用者負責。
說明:對於模塊間接口函數的參數的合法性檢查這一問題,每每有兩個極端現象,即:要
麼是調用者和被調用者對參數均不做合法性檢查,結果就遺漏了合法性檢查這一必要的處理過程,形成問題隱患;要麼就是調用者和被調用者均對參數進行合法性檢查,這種狀況雖不會形成問題,但產生了冗餘代碼,下降了效率。
7.6 其餘
7.6.1 防止將函數的參數做爲工做變量。
說明:將函數的參數做爲工做變量,有可能錯誤地改變參數內容,因此很危險。對必須改
變的參數,最好先用局部變量代之,最後再將該局部變量的內容賦給該參數。
示例:下函數的實現不太好。
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;
}
7.6.2 函數的規模儘可能限制在200行之內。
說明:不包括註釋和空格行。
7.6.3 一個函數僅完成一件功能。
7.6.4 爲簡單功能編寫函數。
說明:雖然爲僅用一兩行就可完成的功能去編函數好象沒有必要,但用函數可以使功能明確
化,增長程序可讀性,亦可方便維護、測試。
示例:以下語句的功能不很明顯。
value = ( a > b ) ? a : b ;
改成以下就很清晰了。
int max (int a, int b)
{
return ((a > b) ? a : b);
}
value = max (a, b);
或改成以下。
value = MAX (a, b);
7.6.5 不要設計多用途面面俱到的函數。
說明:多功能集於一身的函數,極可能使函數的理解、測試、維護等變得困難。
7.6.6 函數的功能應該是能夠預測的,也就是隻要輸入數據相同就應產生一樣的輸出。
說明:帶有內部「存儲器」的函數的功能多是不可預測的,由於它的輸出可能取決於內
部存儲器(如某標記)的狀態。這樣的函數既不易於理解又不利於測試和維護。在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;
}
7.6.7 儘可能不要編寫依賴於其餘函數內部實現的函數。
說明:此條爲函數獨立性的基本要求。因爲目前大部分高級語言都是結構化的,因此經過
具體語言的語法要求與編譯器功能,基本就能夠防止這種狀況發生。但在彙編語言中,因爲其靈活性,極可能使函數出現這種狀況。
示例:以下是在DOS下TASM的彙編程序例子。過程Print_Msg的實現依賴於Input_Msg的具體實現,這種程序是非結構化的,難以維護、修改。
… // 程序代碼
proc Print_Msg // 過程(函數)Print_Msg
… // 程序代碼
jmp LABEL
… // 程序代碼
endp
proc Input_Msg // 過程(函數)Input_Msg
… // 程序代碼
LABEL:
… // 程序代碼
endp
7.6.8 避免設計多參數函數,不使用的參數從接口中去掉。
說明:目的減小函數間接口的複雜度。
7.6.9 非調度函數應減小或防止控制參數,儘可能只使用數據參數。
說明:本建議目的是防止函數間的控制耦合。調度函數是指根據輸入的消息類型或控制命
令,來啓動相應的功能實體(即函數或過程),而自己並不完成具體功能。控制參數是指改變函數功能行爲的參數,即函數要根據此參數來決定具體怎樣工做。非調度函數的控制參數增長了函數間的控制耦合,極可能使函數間的耦合度增大,並使函數的功能不惟一。
示例:以下函數構造不太合理。
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);
}
7.6.10 檢查函數全部參數輸入的有效性。
7.6.11 檢查函數全部非參數輸入的有效性,如數據文件、公共變量等。
說明:函數的輸入主要有兩種:一種是參數輸入;另外一種是全局變量、數據文件的輸入,
即非參數輸入。函數在使用輸入以前,應進行必要的檢查。
7.6.12 函數名應準確描述函數的功能。
7.6.13 使用動賓詞組爲執行某操做的函數命名。若是是OOP方法,能夠只有動詞(名詞是對象自己)。
示例:參照以下方式命名函數。
void print_record( unsigned int rec_ind ) ;
int input_record( void ) ;
unsigned char get_current_color( void ) ;
7.6.14 避免使用無心義或含義不清的動詞爲函數命名。
說明:避免用含義不清的動詞如process、handle等爲函數命名,由於這些動詞並無
說明要具體作什麼。
7.6.15 函數的返回值要清楚、明瞭,讓使用者不容易忽視錯誤狀況。
說明:函數的每種出錯返回值的意義要清晰、明瞭、準確,防止使用者誤用、理解錯誤或
忽視錯誤返回碼。
7.6.16 除非必要,最好不要把與函數返回值類型不一樣的變量,以編譯系統默認的轉換方式或強制的轉換方式做爲返回值返回。
7.6.17 讓函數在調用點顯得易懂、容易理解。
7.6.18 在調用函數填寫參數時,應儘可能減小沒有必要的默認數據類型轉換或強制數據類型轉換。
說明:由於數據類型轉換或多或少存在危險。
7.6.19 避免函數中沒必要要語句,防止程序中的垃圾代碼。
說明:程序中的垃圾代碼不只佔用額外的空間,並且還經常影響程序的功能與性能,很可
能給程序的測試、維護等形成沒必要要的麻煩。
7.6.20 防止把沒有關聯的語句放到一個函數中。
說明:防止函數或過程內出現隨機內聚。隨機內聚是指將沒有關聯或關聯很弱的語句放到
同一個函數或過程當中。隨機內聚給函數或過程的維護、測試及之後的升級等形成了不便,同時也使函數或過程的功能不明確。使用隨機內聚函數,經常容易出如今一種應用場合須要改進此函數,而另外一種應用場合又不容許這種改進,從而陷入困境。
在編程時,常常遇到在不一樣函數中使用相同的代碼,許多開發人員都願把這些代碼提出來,
並構成一個新函數。若這些代碼關聯較大而且是完成一個功能的,那麼這種構造是合理的,不然這種構造將產生隨機內聚的函數。
示例:以下函數就是一種隨機內聚。
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; /* 初始化「點」的座標 */
}
7.6.21 若是多段代碼重複作同一件事情,那麼在函數的劃分上可能存在問題。
說明:若此段代碼各語句之間有實質性關聯而且是完成同一件功能的,那麼可考慮把此段
代碼構形成一個新的函數。
7.6.22 功能不明確較小的函數,特別是僅有一個上級函數調用它時,應考慮把它合併到上級函數中,而沒必要單獨存在。
說明:模塊中函數劃分的過多,通常會使函數間的接口變得複雜。因此太小的函數,特別
是扇入很低的或功能不明確的函數,不值得單獨存在。
7.6.23 設計高扇入、合理扇出(小於7)的函數。
說明:扇出是指一個函數直接調用(控制)其它函數的數目,而扇入是指有多少上級函數
調用它。
扇出過大,代表函數過度複雜,須要控制和協調過多的下級函數;而扇出太小,如老是1,
代表函數的調用層次可能過多,這樣不利程序閱讀和函數結構的分析,而且程序運行時會對系統資源如堆棧空間等形成壓力。函數較合理的扇出(調度函數除外)一般是3-5。扇出太大,通常是因爲缺少中間層次,可適當增長中間層次的函數。扇出過小,可把下級函數進一步分解多個函數,或合併到上級函數中。固然分解或合併函數時,不能改變要實現的功能,也不能違背函數間的獨立性。
扇入越大,代表使用此函數的上級函數越多,這樣的函數使用效率高,但不能違背函數間
的獨立性而單純地追求高扇入。公共模塊中的函數及底層函數應該有較高的扇入。
較良好的軟件結構一般是頂層函數的扇出較高,中層函數的扇出較少,而底層函數則扇入
到公共模塊中。
7.6.24 減小函數自己或函數間的遞歸調用。
說明:遞歸調用特別是函數間的遞歸調用(如A->B->C->A),影響程序的可理解性;遞
歸調用通常都佔用較多的系統資源(如棧空間);遞歸調用對程序的測試有必定影響。故除非爲某些算法或功能的實現方便,應減小不必的遞歸調用。
7.6.25 仔細分析模塊的功能及性能需求,並進一步細分,同時如有必要畫出有關數據流圖,據此來進行模塊的函數劃分與組織。
說明:函數的劃分與組織是模塊的實現過程當中很關鍵的步驟,如何劃分出合理的函數結構,
關係到模塊的最終效率和可維護性、可測性等。根據模塊的功能圖或/及數據流圖映射出函數結構是經常使用方法之一。
7.6.26 改進模塊中函數的結構,下降函數間的耦合度,並提升函數的獨立性以及代碼可讀性、效率和可維護性。優化函數結構時,要遵照如下原則:
(1)不能影響模塊功能的實現。
(2)仔細考查模塊或函數出錯處理及模塊的性能要求並進行完善。
(3)經過分解或合併函數來改進軟件結構。
(4)考查函數的規模,過大的要進行分解。
(5)下降函數間接口的複雜度。
(6)不一樣層次的函數調用要有較合理的扇入、扇出。
(7)函數功能應可預測。
(8)提升函數內聚。(單一功能的函數內聚最高)
說明:對初步劃分後的函數結構應進行改進、優化,使之更爲合理。
7.6.27 在多任務操做系統的環境下編程,要注意函數可重入性的構造。
說明:可重入性是指函數能夠被多個任務進程調用。在多任務操做系統中,函數是否具備
可重入性是很是重要的,由於這是多個進程能夠共用此函數的必要條件。另外,編譯器是否提供可重入函數庫,與它所服務的操做系統有關,只有操做系統是多任務時,編譯器纔有可能提供可重入函數庫。如DOS下BC和MSC等就不具有可重入函數庫,由於DOS是單用戶單任務操做系統。
7.6.28 避免使用BOOL參數。
說明:緣由有二,其一是BOOL參數值無心義,TURE/FALSE的含義是很是模糊的,在調
用時很難知道該參數到底傳達的是什麼意思;其二是BOOL參數值不利於擴充。還有NULL也是一個無心義的單詞。
7.6.29 對於提供了返回值的函數,在引用時最好使用其返回值。
7.6.30 當一個過程(函數)中對較長變量(通常是結構的成員)有較多引用時,能夠用一個意義至關的宏代替。
說明:這樣能夠增長編程效率和程序的可讀性。
示例:在某過程當中較多引用TheReceiveBuffer[FirstSocket].byDataPtr,
則能夠經過如下宏定義來代替:
8 可測性
8.1 調測開關
在同一項目組或產品組內,要有一套統一的爲集成測試與系統聯調準備的調測開關及相應打印函數,而且要有詳細的說明。
說明:本規則是針對項目組或產品組的。
8.2 打印信息
在同一項目組或產品組內,調測打印出的信息串的格式要有統一的形式。信息串中至少要有所在模塊名(或源文件名)及行號。
說明:統一的調測信息格式便於集成測試。
8.3 單元測試
編程的同時要爲單元測試選擇恰當的測試點,並仔細構造測試代碼、測試用例,同時給出明確的註釋說明。測試代碼部分應做爲(模塊中的)一個子模塊,以方便測試代碼在模塊中的安裝與拆卸(經過調測開關)。
說明:爲單元測試而準備。
8.4 集成測試
在進行集成測試/系統聯調以前,要構造好測試環境、測試項目及測試用例,同時仔細分析並優化測試用例,以提升測試效率。
說明:好的測試用例應儘量模擬出程序所遇到的邊界值、各類複雜環境及一些極端狀況等。
8.5 斷言使用
8.5.1 使用斷言來發現軟件問題,提升代碼可測性。
說明:斷言是對某種假設條件進行檢查(可理解爲若條件成立則無動做,不然應報告),
它能夠快速發現並定位軟件問題,同時對系統錯誤進行自動報警。斷言能夠對在系統中隱藏很深,用其它手段極難發現的問題進行定位,從而縮短軟件問題定位時間,提升系統的可測性。實際應用時,可根據具體狀況靈活地設計斷言。
示例:下面是C語言中的一個斷言,用宏來設計的。(其中NULL爲0L)
void exam_assert( char * file_name, unsigned int line_no )
{
printf( 「\n[EXAM]Assert failed: %s, line %u\n」,
file_name, line_no );
abort( );
}
if (condition) // 若條件成立,則無動做 NULL; else // 不然報告 exam_assert( __FILE__, __LINE__ )
8.5.2 用斷言來檢查程序正常運行時不該發生但在調測時有可能發生的非法狀況。
8.5.3 不能用斷言來檢查最終產品確定會出現且必須處理的錯誤狀況。
說明:斷言是用來處理不該該發生的錯誤狀況的,對於可能會發生的且必須處理的狀況要
寫防錯程序,而不是斷言。如某模塊收到其它模塊或鏈路上的消息後,要對消息的合理性進行檢查,此過程爲正常的錯誤檢查,不能用斷言來實現。
8.5.4 對較複雜的斷言加上明確的註釋。
說明:爲複雜的斷言加註釋,可澄清斷言含義並減小沒必要要的誤用。
8.5.5 用斷言確認函數的參數。
示例:假設某函數參數中有一個指針,那麼使用指針前可對它檢查,以下。
int exam_fun( unsigned char *str )
{
EXAM_ASSERT( str != NULL ); // 用斷言檢查「假設指針不爲空」這個條件
... //other program code
}
8.5.6 用斷言保證沒有定義的特性或功能不被使用。
示例:假設某通訊模塊在設計時,準備提供「無鏈接」和「鏈接」 這兩種業務。但當前
的版本中僅實現了「無鏈接」業務,且在此版本的正式發行版中,用戶(上層模塊)不該產生「鏈接」業務的請求,那麼在測試時可用斷言檢查用戶是否使用「鏈接」業務。以下。
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
}
8.5.7 用斷言對程序開發環境(OS/Compiler/Hardware)的假設進行檢查。
說明:程序運行時所需的軟硬件環境及配置要求,不能用斷言來檢查,而必須由一段專門
代碼處理。用斷言僅可對程序開發環境中的假設及所配置的某版本軟硬件是否具備某種功能的假設進行檢查。如某網卡是否在系統運行環境中配置了,應由程序中正式代碼來檢查;而此網卡是否具備某設想的功能,則可由斷言來檢查。
對編譯器提供的功能及特性假設可用斷言檢查,緣由是軟件最終產品(即運行代碼或機器
碼)與編譯器已沒有任何直接關係,即軟件運行過程當中(注意不是編譯過程當中)不會也不該該對編譯器的功能提出任何需求。
示例:用斷言檢查編譯器的int型數據佔用的內存空間是否爲2,以下。
EXAM_ASSERT( sizeof( int ) == 2 );
8.5.8 正式軟件產品中應把斷言及其它調測代碼去掉(即把有關的調測開關關掉)。
說明:加快軟件運行速度。
8.6 設置與取消有關測試手段時,不能影響軟件功能功能
說明:即有測試代碼的軟件和關掉測試代碼的軟件,在功能行爲上應一致。
8.7 版本維護
8.7.1 用調測開關來切換軟件的DEBUG版和正式版,而不要同時存在正式版本和DEBUG版本的不一樣源文件,以減小維護的難度。
8.7.2 軟件的DEBUG版本和發行版本應該統一維護,不容許分家,而且要時刻注意保證兩個版本在實現功能上的一致性。
8.8 其餘
8.8.1 在編寫代碼以前,應預先設計好程序調試與測試的方法和手段,並設計好各類調測開關及相應測試代碼如打印函數等。
說明:程序的調試與測試是軟件生存週期中很重要的一個階段,如何對軟件進行較全面、高率的測試並儘量地找出軟件中的錯誤就成爲很關鍵的問題。所以在編寫源代碼以前,除了要有一套比較完善的測試計劃外,還應設計出一系列代碼測試手段,爲單元測試、集成測試及系統聯調提供方便。
8.8.2 調測開關應分爲不一樣級別和類型。
說明:調測開關的設置及分類應從如下幾方面考慮:針對模塊或系統某部分代碼的調測;
針對模塊或系統某功能的調測;出於某種其它目的,如對性能、容量等的測試。這樣作便於軟件功能的調測,而且便於模塊的單元測試、系統聯調等。
8.8.3 編寫防錯程序,而後在處理錯誤以後可用斷言宣佈發生錯誤。
示例:假如某模塊收到通訊鏈路上的消息,則應對消息的合法性進行檢查,若消息類別不
是通訊協議中規定的,則應進行出錯處理,以後可用斷言報告,以下例。
/* 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 );
}
if ( condition ) // 若條件成立,則無動做 NULL; else // 不然報告 assert_report ( __FILE__, __LINE__ )
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; }
}
9 程序效率
9.1 編程時要常常注意代碼的效率。
說明:代碼效率分爲全局效率、局部效率、時間效率及空間效率。全局效率是站在整個系
統的角度上的系統效率;局部效率是站在模塊或函數角度上的效率;時間效率是程序處理輸入任務所需的時間長短;空間效率是程序所需內存空間,如機器代碼空間大小、數據空間大小、棧空間大小等。
9.2 提升代碼效率
在保證軟件系統的正確性、穩定性、可讀性及可測性的前提下,提升代碼效率。
說明:不能一味地追求代碼效率,而對軟件的正確性、穩定性、可讀性及可測性形成影響。
9.3 全局效率高於局部效率
局部效率應爲全局效率服務,不能由於提升局部效率而對全局效率形成影響。
9.4 提升代碼空間效率
經過對系統數據結構的劃分與組織的改進,以及對程序算法的優化來提升空間效率。
說明:這種方式是解決軟件空間效率的根本辦法。
示例:以下記錄學生學習成績的結構不合理。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef struct STUDENT_SCORE_STRU
BYTE name[8]; BYTE age; BYTE sex; BYTE class; BYTE subject; float score;
} STUDENT_SCORE;
由於每位學生都有多科學習成績,故如上結構將佔用較大空間。應以下改進(分爲兩個結構),總的存貯空間將變小,操做也變得更方便。
typedef struct STUDENT_STRU
{
BYTE name[8];
BYTE age;
BYTE sex;
BYTE class;
} STUDENT;
typedef struct STUDENT_SCORE_STRU
{
WORD student_index;
BYTE subject;
float score;
} STUDENT_SCORE;
9.5 循環體內工做量最小化
說明:應仔細考慮循環體內的語句是否能夠放在循環體以外,使循環體內工做量最小,從
而提升程序的時間效率。
示例:以下代碼效率不高。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
back_sum = sum; /* backup sum */
}
語句「back_sum = sum;」徹底能夠放在for語句以後,以下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
}
back_sum = sum; /* backup sum */
9.6 其餘
9.6.1 仔細分析有關算法,並進行優化。
9.6.2 仔細考查、分析系統及模塊處理輸入(如事務、消息等)的方式,並加以改進。
9.6.3 對模塊中函數的劃分及組織方式進行分析、優化,改進模塊中函數的組織結構,提升程序效率。
說明:軟件系統的效率主要與算法、處理任務方式、系統功能及函數結構有很大關係,僅
在代碼上下功夫通常不能解決根本問題。
9.6.4 編程時,要隨時留心代碼效率;優化代碼時,要考慮周全。
9.6.5 不該花過多的時間拼命地提升調用不很頻繁的函數代碼效率。
說明:對代碼優化可提升效率,但若考慮不周頗有可能引發嚴重後果。
9.6.6 要仔細地構造或直接用匯編編寫調用頻繁或性能要求極高的函數。
說明:只有對編譯系統產生機器碼的方式以及硬件系統較爲熟悉時,纔可以使用匯編嵌入方
式。嵌入彙編可提升時間及空間效率,但也存在必定風險。
9.6.7 在保證程序質量的前提下,經過壓縮代碼量、去掉沒必要要代碼以及減小沒必要要的局部和全局變量,來提升空間效率。
說明:這種方式對提升空間效率可起到必定做用,但每每不能解決根本問題。
9.6.8 在多重循環中,應將最忙的循環放在最內層。
說明:減小CPU切入循環層的次數。
示例:以下代碼效率不高。
for (row = 0; row < 100; row++)
{
for (col = 0; col < 5; col++)
{
sum += a[row][col];
}
}
能夠改成以下方式,以提升效率。
for (col = 0; col < 5; col++)
{
for (row = 0; row < 100; row++)
{
sum += a[row][col];
}
}
9.6.9 儘可能減小循環嵌套層次。
9.6.10 避免循環體內含判斷語句,應將循環語句置於判斷語句的代碼塊之中。
說明:目的是減小判斷次數。循環體中的判斷語句是否能夠移到循環體外,要視程序的具
體狀況而言,通常狀況,與循環變量無關的判斷語句能夠移到循環體外,而有關的則不能夠。
示例:以下代碼效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
if (data_type == RECT_AREA)
{
area_sum += rect_area[ind];
}
else
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
由於判斷語句與循環變量無關,故可以下改進,以減小判斷次數。
if (data_type == RECT_AREA)
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
area_sum += rect_area[ind];
}
}
else
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
9.6.11 儘可能用乘法或其它方法代替除法,特別是浮點運算中的除法。
說明:浮點運算除法要佔用較多CPU資源。
示例:以下表達式運算可能要佔較多CPU資源。
radius = circle_length / (2 * PAI);
應以下把浮點除法改成浮點乘法。
radius = circle_length * PAI_RECIPROCAL / 2;
9.6.12 不要一味追求緊湊的代碼。
說明:由於緊湊的代碼並不表明高效的機器碼。
10 質量保證
10.1 在軟件設計過程當中構築軟件質量。
10.2 代碼質量保證優先原則
(1)正確性,指程序要實現設計要求的功能。
(2)穩定性、安全性,指程序穩定、可靠、安全。
(3)可測試性,指程序要具備良好的可測試性。
(4)規範/可讀性,指程序書寫風格、命名規則等要符合規範。
(5)全局效率,指軟件系統的總體效率。
(6)局部效率,指某個模塊/子模塊/函數的自己效率。
(7)我的表達方式/我的方便性,指我的編程習慣。
10.3 只引用屬於本身的存貯空間。
說明:若模塊封裝的較好,那麼通常不會發生非法引用他人的空間。
10.4 防止引用已經釋放的內存空間。
說明:在實際編程過程當中,稍不留心就會出如今一個模塊中釋放了某個內存塊(如C語言
指針),而另外一模塊在隨後的某個時刻又使用了它。要防止這種狀況發生。
10.5 內存及時釋放
過程/函數中分配的內存,在過程/函數退出以前要釋放。
10.6 文件句柄及時關閉
過程/函數中申請的(爲打開文件而使用的)文件句柄,在過程/函數退出以前要關閉。
說明:分配的內存不釋放以及文件句柄不關閉,是較常見的錯誤,並且稍不注意就有可能
發生。這類錯誤每每會引發很嚴重後果,且難以定位。
示例:下函數在退出以前,沒有把分配的內存釋放。
typedef unsigned char BYTE;
int example_fun( BYTE gt_len, BYTE *gt_code )
{
BYTE *gt_buf;
gt_buf = (BYTE *) malloc (MAX_GT_LENGTH); ... //program code, include check gt_buf if or not NULL. /* global title length error */ if (gt_len > MAX_GT_LENGTH) { return GT_LENGTH_ERROR; // 忘了釋放gt_buf } ... // other program code
}
應改成以下。
int example_fun( BYTE gt_len, BYTE *gt_code )
{
BYTE *gt_buf;
gt_buf = (BYTE * ) malloc ( MAX_GT_LENGTH ); ... // program code, include check gt_buf if or not NULL. /* global title length error */ if (gt_len > MAX_GT_LENGTH) { free( gt_buf ); // 退出以前釋放gt_buf return GT_LENGTH_ERROR; } ... // other program code
}
10.7 防止內存操做越界
說明:內存操做主要是指對數組、指針、內存地址等的操做。內存操做越界是軟件系統主
要錯誤之一,後果每每很是嚴重,因此當咱們進行這些操做時必定要仔細當心。
示例:假設某軟件系統最多可由10個用戶同時使用,用戶號爲1-10,那麼以下程序存在
問題。
unsigned char usr_login_flg[MAX_USR_NUM]= 「」;
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no])
{
usr_login_flg[usr_no]= TRUE;
}
}
當usr_no爲10時,將使用usr_login_flg越界。可採用以下方式解決。
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no - 1])
{
usr_login_flg[usr_no - 1]= TRUE;
}
}
10.8 認真處理程序所能遇到的各類出錯狀況
10.9 初始化變量
系統運行之初,要初始化有關變量及運行環境,防止未經初始化的變量被引用。
10.10 數據一致性檢查
系統運行之初,要對加載到系統中的數據進行一致性檢查。
說明:使用不一致的數據,容易使系統進入混亂狀態和不可知狀態。
10.11 嚴禁隨意更改其它模塊或系統的有關設置和配置
說明:編程時,不能爲所欲爲地更改不屬於本身模塊的有關設置如常量、數組的大小等。
10.12 不能隨意改變與其它模塊的接口
10.13 系統接口
充分了解系統的接口以後,再使用系統提供的功能。
示例:在B型機的各模塊與操做系統的接口函數中,有一個要由各模塊負責編寫的初始化過程,此過
程在軟件系統加載完成後,由操做系統發送的初始化消息來調度。所以就涉及到初始化消息的類型與消息發送的順序問題,特別是消息順序,若沒搞清楚就開始編程,很容易引發嚴重後果。如下示例引自B型曾出現過的實際代碼,其中使用了FID_FETCH_DATA與FID_INITIAL初始化消息類型,注意B型機的系統是在FID_FETCH_DATA以前發送FID_INITIAL的。
MID alarm_module_list[MAX_ALARM_MID];
int FAR SYS_ALARM_proc( FID function_id, int handle )
{
_UI i, j;
switch ( function_id ) { ... // program code case FID_INITAIL: for (i = 0; i < MAX_ALARM_MID; i++) { if (alarm_module_list[i]== BAM_MODULE // **) || (alarm_module_list[i]== LOCAL_MODULE) { for (j = 0; j < ALARM_CLASS_SUM; j++) { FAR_MALLOC( ... ); } } } ... // program code break; case FID_FETCH_DATA: ... // program code Get_Alarm_Module( ); // 初始化alarm_module_list break; ... // program code }
}
因爲FID_INITIAL是在FID_FETCH_DATA以前執行的,而初始化
alarm_module_list是在FID_FETCH_DATA中進行的,故在FID_INITIAL中(**)處引用alarm_module_list變量時,它尚未被初始化。這是個嚴重錯誤。
應以下改正:要麼把Get_Alarm_Module函數放在FID_INITIAL中(**)以前;要
麼就必須考慮(**)處的判斷語句是否能夠用(不使用alarm_module_list變量的)其它方式替代,或者是否能夠取消此判斷語句。
10.14 編程時,要防止差1錯誤
說明:此類錯誤通常是因爲把「<=」誤寫成「<」或「>=」誤寫成「>」等形成的,由此
引發的後果,不少狀況下是很嚴重的,因此編程時,必定要在這些地方當心。當編完程序後,應對這些操做符進行完全檢查。
10.15 操做符檢查
要時刻注意易混淆的操做符。當編完程序後,應從頭到尾檢查一遍這些操做符,以防止拼寫錯誤。
說明:形式相近的操做符最容易引發誤用,如C/C++中的「=」與「==」、「|」與「||」、
「&」與「&&」等,若拼寫錯了,編譯器不必定可以檢查出來。
示例:如把「&」寫成「&&」,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);
被寫爲:
ret_flg = (pmsg->ret_flg && RETURN_MASK);
rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被寫爲:
rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
10.16 分支語句寫完整
有可能的話,if語句儘可能加上else分支,對沒有else分支的語句要當心對待;switch語句必須有default分支。
10.17 使用return語句
Unix下,多線程的中的子線程退出必需採用主動退出方式,即子線程應return出口。
10.18 不要濫用goto語句
說明:goto語句會破壞程序的結構性,因此除非確實須要,最好不使用goto語句。
10.19 其餘
10.19.1 不使用與硬件或操做系統關係很大的語句,而使用建議的標準語句,以提升軟件的可移植性和可重用性。
10.19.2 除非爲了知足特殊需求,避免使用嵌入式彙編。
說明:程序中嵌入式彙編,通常都對可移植性有較大的影響。
10.19.3 精心地構造、劃分子模塊,並按「接口」部分及「內核」部分合理地組織子模塊,以提升「內核」部分的可移植性和可重用性。
說明:對不一樣產品中的某個功能相同的模塊,若能作到其內核部分徹底或基本一致,那麼
不管對產品的測試、維護,仍是對之後產品的升級都會有很大幫助。
10.19.4 精心構造算法,並對其性能、效率進行測試。
10.19.5 對較關鍵的算法最好使用其它算法來確認。
10.19.6 時刻注意表達式是否會上溢、下溢。
示例:以下程序將形成變量下溢。
unsigned char size ;
while (size– >= 0) // 將出現下溢
{
… // program code
}
當size等於0時,再減1不會小於0,而是0xFF,故程序是一個死循環。應以下修改。
char size; // 從unsigned char 改成char
while (size– >= 0)
{
… // program code
}
10.19.7 使用變量時要注意其邊界值的狀況。
示例:如C語言中字符型變量,有效值範圍爲-128到127。故如下表達式的計算存在一
定風險。
char chr = 127;
int sum = 200;
chr += 1; // 127爲chr的邊界值,再加1將使chr上溢到-128,而不是128。
sum += chr; // 故sum的結果不是328,而是72。
若chr與sum爲同一種類型,或表達式按以下方式書寫,可能會好些。
sum = sum + chr + 1;
10.19.8 留心程序機器碼大小(如指令空間大小、數據空間大小、堆棧空間大小等)是否超出系統有關限制。
10.19.9 爲用戶提供良好的接口界面,使用戶能較充分地瞭解系統內部運行狀態及有關係統出錯狀況。
10.19.10 系統應具備必定的容錯能力,對一些錯誤事件(如用戶誤操做等)能進行自動補救。
10.19.11 對一些具備危險性的操做代碼(如寫硬盤、刪數據等)要仔細考慮,防止對數據、硬件等的安全構成危害,以提升系統的安全性。
10.19.12 第三方軟件開發工具包
使用第三方提供的軟件開發工具包或控件時,要注意如下幾點:
(1)充分了解應用接口、使用環境及使用時注意事項。
(2)不能過度相信其正確性。
(3)除非必要,不要使用不熟悉的第三方工具包與控件。
說明:使用工具包與控件,可加快程序開發速度,節省時間,但使用以前必定對它有較充
分的瞭解,同時第三方工具包與控件也有可能存在問題。
10.19.13 資源文件
資源文件(多語言版本支持),若是資源是對語言敏感的,應讓該資源與源代碼文件脫離,具體方法有下面幾種:使用單獨的資源文件、DLL文件或其它單獨的描述文件(如數據庫格式)。
11 代碼編輯、編譯、審查
11.1 打開編譯器的全部告警開關對程序進行編譯
11.2 在產品軟件(項目組)中,要統一編譯開關選項
11.3 經過代碼走讀及審查方式對代碼進行檢查。
說明:代碼走讀主要是對程序的編程風格如註釋、命名等以及編程時易出錯的內容進行檢
查,可由開發人員本身或開發人員交叉的方式進行;代碼審查主要是對程序實現的功能及程序的穩定性、安全性、可靠性等進行檢查及評審,可經過自審、交叉審覈或指定部門抽查等方式進行。
11.4 測試部測試產品以前,應對代碼進行抽查及評審
11.5 其餘
11.5.1 編寫代碼時要注意隨時保存,並按期備份,防止因爲斷電、硬盤損壞等緣由形成代碼丟失。
11.5.2 同產品軟件(項目組)內,最好使用相同的編輯器,並使用相同的設置選項。
說明:同一項目組最好採用相同的智能語言編輯器,如Muiti Editor,Visual Editor
等,並設計、使用一套縮進宏及註釋宏等,將縮進等問題交由編輯器處理。
11.5.3 要當心地使用編輯器提供的塊拷貝功能編程。
說明:當某段代碼與另外一段代碼的處理功能類似時,許多開發人員都用編輯器提供的塊拷
貝功能來完成這段代碼的編寫。因爲程序功能相近,故所使用的變量、採用的表達式等在功能及命名上可能都很相近,因此使用塊拷貝時要注意,除了修改相應的程序外,必定要把使用的每一個變量仔細查看一遍,以改爲正確的。不該期望編譯器能查出全部這種錯誤,好比當使用的是全局變量時,就有可能使某種錯誤隱藏下來。
11.5.4 合理地設計軟件系統目錄,方便開發人員使用。
說明:方便、合理的軟件系統目錄,可提升工做效率。目錄構造的原則是方便有關源程序
的存儲、查詢、編譯、連接等工做,同時目錄中還應具備工做目錄—-全部的編譯、連接等工做應在此目錄中進行,工具目錄—-有關文件編輯器、文件查找等工具可存放在此目錄中。
11.5.5 某些語句經編譯後產生告警,但若是你認爲它是正確的,那麼應經過某種手段去掉告警信息。
說明:在Borland C/C++中,可用「#pragma warn」來關掉或打開某些告警。
示例:
int examples_fun( void )
{
// 程序,但無return語句。
}
編譯函數examples_fun時本應產生「函數應有返回值」告警,但因爲關掉了此告警信
息顯示,因此編譯時將不會產生此告警提示。
11.5.6 使用代碼檢查工具(如C語言用PC-Lint)對源程序檢查。
11.5.7 使用軟件工具(如 LogiSCOPE)進行代碼審查。
12 代碼測試、維護
12.1 單元測試要求至少達到語句覆蓋
12.2 單元測試開始要跟蹤每一條語句,並觀察數據流及變量的變化
12.3 清理、整理或優化後的代碼要通過審查及測試。
12.4 代碼版本升級要通過嚴格測試
12.5 使用工具軟件對代碼版本進行維護
12.6 正式版本上軟件的任何修改都應有詳細的文檔記錄
12.7 其餘
12.7.1 發現錯誤當即修改,而且要記錄下來。
12.7.2 關鍵的代碼在彙編級跟蹤。
12.7.3 仔細設計並分析測試用例,使測試用例覆蓋儘量多的狀況,以提升測試用例的效率。
12.7.4 儘量模擬出程序的各類出錯狀況,對出錯處理代碼進行充分的測試。
12.7.5 仔細測試代碼處理數據、變量的邊界狀況。
12.7.6 保留測試信息,以便分析、總結經驗及進行更充分的測試。
12.7.7 不該經過「試」來解決問題,應尋找問題的根本緣由。
12.7.8 對自動消失的錯誤進行分析,搞清楚錯誤是如何消失的。
12.7.9 修改錯誤不只要治表,更要治本。
12.7.10 測試時應設法使不多發生的事件常常發生。
12.7.11 明確模塊或函數處理哪些事件,並使它們常常發生。
12.7.12 堅持在編碼階段就對代碼進行完全的單元測試,不要等之後的測試工做來發現問題。
12.7.13 去除代碼運行的隨機性(如去掉無用的數據、代碼及儘量防止並注意函數中的「內部寄存器」等),讓函數運行的結果可預測,並使出現的錯誤可再現。
13 宏
13.1 用宏定義表達式時,要使用完備的括號
示例:以下定義的宏都存在必定的風險。
正確的定義應爲:
13.2 將宏所定義的多條表達式放在大括號中
示例:下面的語句只有宏的第一條表達式被執行。爲了說明問題,for語句的書寫稍不符規範。
a = 0;\ b = 0;
for (index = 0; index < RECT_TOTAL_NUM; index++)
INTI_RECT_VALUE( rect.a, rect.b );
正確的用法應爲:
{\
a = 0;\
b = 0;\
}
for (index = 0; index < RECT_TOTAL_NUM; index++)
{
INTI_RECT_VALUE( rect[index].a, rect[index].b );
}
13.3 使用宏時,不容許參數發生變化
示例:以下用法可能致使錯誤。
int a = 5;
int b;
b = SQUARE( a++ ); // 結果:a = 7,即執行了兩次增1。
正確的用法是: b = SQUARE( a ); a++; // 結果:a = 6,即只執行了一次增1。