【简洁的代码永远不会掩盖设计者的意图】如何写出规范整洁的代码

简介: 【简洁的代码永远不会掩盖设计者的意图】如何写出规范整洁的代码

一、编程规范的作用

1.1 提高源程序的可读性和可维护性

1.2 降低错误的机会

1.3 提高源代码可重用性和质量

二、规范的三种形式

2.1 原则:编程时应该坚持的指导思想。

2.2 规则:编程时必须遵守的约定。

2.3 建议:编程时必须加以考虑的约定。

三、规范的内容

3.1 基本原则

3.1.1 原则一

首先是为人编写程序,其次才是计算机。

这是软件开发的基本要点,软件的生命周期贯穿产品的开发、测试、生产、用户使用、版本升级和后期维护等长期过程,只有易读、易维护的软件代码才具有生命力。

3.1.2 原则二

保持代码的简明清晰,避免过分的编程技巧。

简单是最美。保持代码的简单化是软件工程化的基本要求。不要过分追求技巧,否则会降低程序的可读性。

3.1.3 原则三

所有的代码尽量遵循ANSI C标准。

所有的代码尽可能遵循ANSI C标准,尽可能不使用ANSI C未定义的或编译器扩展的功能。

3.1.4 原则四

编程时首先达到正确性,其次考虑效率。

编程首先考虑的是满足正确性、健壮性、可维护性、可移植性等质量因素,最后才考虑程序的效率和资源占用。

3.1.5 原则五

避免或少用全局变量。

过多地使用全局变量,会导致模块间的紧耦合,违反模块化的要求 。

3.1.6 原则六

尽量避免使用GOTO语句。

3.1.7 原则七

尽可能复用、修正老的代码。

尽量选择可借用的代码,对其修改优化以达到自身要求。

3.1.8 原则八

尽量减少同样的错误出现的次数。

事实上,我们无法做到完全消除错误,但通过不懈的努力,可以减少同样的错误出现的次数

3.2 布局

3.2.1 规则一

遵循统一的布局顺序来书写头文件。

#ifndef 文件名_H(全大写)
#define  文件名_H
其它条件编译选项
#include(依次为标准库头文件、非标准库头文件)
常量定义
全局宏
全局数据类型
类定义
模板(template)(包括C++中的类模板和函数模板)全局函数原型
#endif

3.2.2 规则二

遵循统一的布局顺序来书写实现文件。

文件头注释
#include(依次为标准库头文件、非标准库头文件)
常量定义
文件内部使用的宏
文件内部使用的数据类型
全局变量
本地变量(即静态全局变量)
局部函数原型
类的实现
全局函数
局部函数

3.2.3 规则三

使用注释块分离上面定义的节。

/ ***********************************************************
  *                   数据类型定义                          *
  *********************************************************** /
  typedef unsigned char BOOLEAN;
 
/*************************************************************
  *                      函数原型                            *
  ************************************************************/
  int DoSomething(void);

3.2.4 规则四

头文件必须要避免重复包含。

#ifndef MODULE_H
#define MODULE_H
    
      [文件体]
#endif

 3.2.5 规则五

包含标准库头文件用尖括号 < >,包含非标准库头文件用双引号 “ ”。

#include <stdio.h>
#include “heads.h”

 3.2.6 规则六

遵循统一的顺序书写类的定义及实现。

类的定义(在定义文件中)按如下顺序书写:

公有属性, 公有函数,

保护属性, 保护函数

私有属性,  私有函数

类的实现(在实现文件中)按如下顺序书写:

构造函数,析构函数

公有函数

保护函数

       私有函数

 3.2.7 规则七

程序中一行的代码和注释不能超过80列。

包括空格在内不超过80列。

 3.2.8 规则八

if、else、else if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加 { }。

if (varible1 < varible2) 
{
    varible1 = varible2;
}
反例:    if (varible1 < varible2) varible1 = varible2;  

 3.2.9 规则九

结构型的数组、多维的数组如果在定义时初始化,按照数组的矩阵结构分行书写。

int aiNumbers[4][3] = 
{
    1,  1,   1,
    2,  4,   8,
    3,  9,   27,
    4,  16, 64
}

 3.2.10 规则十

相关的赋值语句等号对齐。

tPDBRes.wHead   =  0;
tPDBRes.wTail   =  wMaxNumOfPDB - 1;
tPDBRes.wFree   =  wMaxNumOfPDB;
tPDBRes.wAddress    =  wPDBAddr;
tPDBRes.wSize   =  wPDBSize;

3.2.11 规则十一

不同逻辑程序块之间要使用空行分隔。

void Foo::Hey(void)
{
   [Hey实现代码]
}
// 空一行
void Foo::Ack(void)
{
   [Ack实现代码]
}

3.2.12 规则十二

一元操作符如“!”、“~”、“++”、“--”、“*”、“&”(地址运算符)等前后不加空格。“[]”、“.”、“->”这类操作符前后不加空格。

!bValue,~iValue,++iCount,*strSource,&fSum,
aiNumber[i] = 5,tBox.dWidth,
tBox->dWidth 

多元运算符和它们的操作数之间至少需要一个空格。

fValue   =  fOldValue;
fTotal   +  fValue
iNumber +=  2;

3.2.13 规则十三

关键字之后要留空格。

if、for、while等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。

函数名之后不要留空格。

函数名后紧跟左括号‘(’,以与关键字区别。

‘(’向后紧跟,‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。

‘,’之后要留空格。‘;’不是行结束符号时其后要留空格。

for凵(i凵=凵0;凵i凵<凵MAX_BSC_NUM;凵i++)
{
  DoSomething(iWidth,凵iHeight);
}

3.2.14 规则十四

注释符与注释内容之间要用一个空格进行分隔。

/* 注释内容 */

// 注释内容

反例:

/*注释内容*/

//注释内容

3.2.15 规则十五

//长表达式(超过80列)要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐
if ((iFormat == CH_A_Format_M) 
   && (iOfficeType == CH_BSC_M))
{ 
     DoSomething();
 }
 
// for循环语句续行在初始化条件语句处对齐
for (long_initialization_statement;
 long_condiction_statement;     
 long_update_statement)
 {
      DoSomething();
 }
 
// 函数声明的续行在第一个参数处对齐
BYTE ReportStatusCheckPara(HWND hWnd, 
                        BYTE ucCallNo, 
                       BYTE ucStatusReportNo); 
 
// 赋值语句的续行应在赋值号处对齐
fTotalBill = fTotalBill + faCustomerPurchases[iID]
         + fSalesTax(faCustomerPurchases[iID]);

3.2.16 规则十六

函数声明时,类型与名称不允许分行书写。

正例:
extern double FAR CalcArea(double dWidth, double dHeight);
反例:
extern double FAR 
CalcArea(double dWidth, double dHeight);

3.3 注释

3.3.1 规则一

C语言的注释符为“/* … */”。C++语言中,多行注释采用“/* … */”,单行注释采用“// …”。

一般情况下,源程序有效注释量必须在20%以上。

注释的原则是有助于对程序的阅读理解,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。有效的注释是指在代码的功能、意图层次上进行注释,提供有用、额外的信息。

3.3.2 规则二

注释使用中文。

对于特殊要求的可以使用英文注释,如工具不支持中文或国际化版本。

文件头部必须进行注释,包括:.h文件、.c文件、.cpp文件、.inc文件、.def文件、编译说明文件.cfg等。

注释必须列出:版权信息、文件标识、内容摘要、版本号、作者、完成日期、修改信息等。

/*********************************************************************
* 版权所有 (C)2024, xxxx股份有限公司。
* 
* 文件名称: // 文件名
* 文件标识: // 见配置管理计划书
* 内容摘要: // 简要描述本文件的内容,包括主要模块、函数及其功能的说明
* 其它说明: // 其它内容的说明
* 当前版本: // 输入当前版本
* 作    者: // 输入作者名字及单位
* 完成日期: // 输入完成日期,例:2000年2月25日
* 
* 修改日期        版本号     修改人       修改内容
 * ---------------------------------------------------------------
   * 2024/02/18      V1.0     XXXX        XXXX
******************************************************************/

3.3.3 规则三

函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、访问和修改的表、修改信息等。

说明:注释必须列出:函数名称、功能描述、输入参数、输出参数、返 回 值、修改信息

/**********************************************************************
* 函数名称: // 函数名称
* 功能描述: // 函数功能、性能等的描述
* 访问的表: //(可选)被访问的表,此项仅对于有数据库操作的程序
* 修改的表: //(可选)被修改的表,此项仅对于有数据库操作的程序
* 输入参数: // 输入参数说明,包括每个参数的作用、取值说明及参数间关系
* 输出参数: // 对输出参数的说明。
  * 返 回 值: // 函数返回值的说明 
   * 其它说明: // 其它说明
* 修改日期        版本号     修改人       修改内容
 * -----------------------------------------------
* 2002/08/01       V1.0     XXXX        XXXX
***********************************************************************/

3.3.4 规则四

包含在{ }中代码块的结束处应加注释,便于阅读。特别是多分支、多重嵌套的条件语句或循环语句。

while (…) 
{
}  /* end of while (…) */    // 指明该条while语句结束

保证代码和注释的一致性。修改代码同时修改相应的注释,不再有用的注释要删除。  

3.3.5 规则五

注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开。

/* 代码段1注释 */
[ 代码段1 ]
 
/* 代码段2注释 */
   [ 代码段2 ] 

3.3.6 规则六。

全局变量要有详细的注释,包括对其功能、取值范围、访问信息及访问时注意事项等的说明。

/*
* 变量作用:(错误状态码)
* 变量范围:例如0 - SUCCESS    1 - Table error   
* 访问说明:(访问的函数以及方法)
*/
   BYTE g_ucTranErrorCode;

3.3.7 规则七。

注释与所描述内容进行同样的缩排。  

int DoSomething(void)
{
/* 代码段1注释 */
    [ 代码段1 ]
 
    /* 代码段2注释 */
    [ 代码段2 ]
} 

3.3.8 规则八。

对分支语句(条件分支、循环语句等)必须编写注释。

通过对函数或过程、变量、结构等正确的命名以及合理地组织代码结构,使代码成为自注释的。

尽量避免在注释中使用缩写,特别是不常用缩写。

3.4 命名规则

3.4.1 规则一

标识符要采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名。标识符应当直观且可以拼读,可望文知义,避免使人产生误解。程序中的英文单词一般不要太复杂,用词应当准确。

标识符只能由26个英文字母,10个数字,及下划线的一个子集来组成,并严格禁止使用连续的下划线,下划线也不能出现在标识符头或结尾(预编译开关除外)。

标识符的命名应当符合“min-length && max-information”原则。

较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个系统使用的专用缩写应该在某处做统一说明。

temp 可缩写为  tmp  ;

flag 可缩写为  flg  ;

statistic 可缩写为  stat ;

increment 可缩写为  inc ;

message   可缩写为  msg ;

程序中不要出现仅靠大小写区分的相似的标识符。

3.4.2 规则二

用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。

add /remove;begin / end;create / destroy   ; insert / delete  ;

first/last; get/release; increment/decrement  ; put / get     ;

lock / unlock ;open / close ; min / max   ;old / new    ;

start / stop   ;next / previous  ;source / target ;

show / hide  ; send / receive ;source / destination     ;

cut / paste    ;  up / down

宏、常量名都要使用大写字母, 用下划线 ‘_’ 分割单词。预编译开关的定义使用下划线 ‘_’ 开始。

DISP_BUF_SIZE、MIN_VALUE、MAX_VALUE

变量名长度应小于31个字符,以保持与ANSI C标准一致。不得取单个字符(如i、j、k等)作为变量名,但是局部循环变量除外。  

3.4.3 规则三

程序中局部变量不要与全局变量重名。

尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。

使用一致的前缀来区分变量的作用域。

变量活动范围前缀规范如下:

  g_      :  全局变量

  m_     :     类的数据成员

  s_      :  模块内静态变量

  空      :  局部变量不加范围前缀

使用一致的小写类型指示符作为前缀来区分变量的类型。

i        : int,                                              f            : float

d       : double                                           c          : char

uc     : unsigned char 或 BYTE                l           : long

p       : pointer                                           b         : BOOL

h       : HANDLE                                       w         : unsigned short 或 WORD

dw    : DWORD或 unsigned long            a          : 数组,array of TYPE

str     : 字符串                                           t          : 结构类型

3.4.4 规则四

完整的变量名应由前缀+变量名主体组成,变量名的主体应当使用“名词”或者“形容词+名词”,且首字母必须大写。

float  g_fValue;        //类型为浮点数的全局变量  
char  *pcOldChar;       //类型为字符指针的局部变量 

函数名用大写字母开头的单词组合而成,且应当使用“动词”或者“动词+名词”(动宾词组)

函数名力求清晰、明了,通过函数名就能够判断函数的主要功能。函数名中不同意义字段之间不要用下划线连接,而要把每个字段的首字母大写以示区分。函数命名采用大小写字母结合的形式,但专有名词不受限制。

结构名、联合名、枚举名由前缀T_ 开头。

事件名由前缀EV_ 开头。

类名采用大小写结合的方法。在构成类名的单词之间不用下划线,类名在开头加上C,类的成员变量统一在前面加m_ 前缀。

尽量避免名字中出现数字编号,如Value1、Value2等,除非逻辑上的确需要编号。

标识符前最好不加项目、产品、部门的标识。这样做的目的是为了代码的可重用性。

3.5 变量、常量与类型

3.5.1 规则一

定义全局变量时必须仔细分析,明确其含义、作用、取值范围及与其它全局变量间的关系。

全局变量关系到程序的结构框架,对于全局变量的理解关系到对整个程序能否正确理解,所以在对全局变量声明的同时,应对其含义、作用及取值范围进行详细地注释说明,若有必要还应说明与其它变量的关系。

明确全局变量与操作此全局变量的函数或过程的关系。

全局变量与函数的关系包括:创建、修改及访问。明确过程操作变量的关系后,将有利于程序的进一步优化、单元测试、系统联调以及代码维护等。这种关系的说明可在注释或文档中描述。

一个变量有且只有一个功能,不能把一个变量用作多种用途。

一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。

3.5.2 规则二

循环语句与判断语句中,不允许对其它变量进行计算与赋值。

 do
 {
      [处理语句]
  } while (cInput = GetChar());   //不允许

3.5.3 规则三

宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来。

宏定义中,对表达式和变量使用括号,可以避免可能发生的计算错误。

正例:

#define  HANDLE(A, B)   (( A ) / ( B ))

反例:

#define  HANDLE(A, B)   (A / B)

3.5.4 规则四

尽量构造仅有一个模块或函数可以修改、创建的全局变量,而其余有关模块或函数只能访问。

对于全局变量通过统一的函数访问。可以避免访问全局变量时引起的错误。

T_Student    g_tStudent;
   T_Student GetStudentValue(void)
            {
      T_Student tStudentValue;
      [获取g_tStudent的访问权]
      tStudentValue = g_tStudent;
      [释放g_tStudent的访问权]
                return tStudentValue;
      }
      BYTE SetStudentValue(const T_Student  *ptStudentValue);  …

3.5.5 规则五

尽量使用const说明常量数据,对于宏定义的常数,必须指出其类型。

1. 正例:
2. const  int   MAX_COUNT = 1000;
3. #define     MAX_COUNT  (int)1000
4. 反例:
5. #define     MAX_COUNT    1000

最好不要在语句块内声明局部变量

3.5.6 规则六

结构和联合必须被类型化。

正例:
const  int   MAX_COUNT = 1000;
#define     MAX_COUNT  (int)1000
反例:
#define     MAX_COUNT    1000

3.5.7 规则七

使用严格形式定义的、可移植的数据类型,尽量不要使用与具体硬件或软件环境关系密切的变量。

使用统一的自定义数据类型,有利于程序的移植,比如win32上

VOID void              BYTE  unsigned char                  INT4  int

3.5.8 规则八

结构是针对一种事务的抽象,功能要单一,不要设计面面俱到的数据结构。

设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。

不同结构间的关系要尽量简单,若两个结构间关系较复杂、密切,那么应合为一个结构。

结构中元素的个数应适中。若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。

3.6 表达式与语句

3.6.1 规则一

一条语句只完成一个功能。

正例:

int  iHelp;  

int  iBase;

int  iResult;

iHelp = iBase

iResult = iHelp + GetValue(&iBase);

反例:

int iBase, iResult;                 // 一行定义多个变量  

iResult = iBase + GetValue(&iBase); // 一条语句实现多个功能,iBase有两种用途。

3.6.2 规则二

在表达式中使用括号,使表达式的运算顺序更清晰。

由于将运算符的优先级与结合律熟记是比较困难的,为了防止产生歧义并提高可读性,即使不加括号时运算顺序不会改变,也应当用括号确定表达式的操作顺序。

正例: 
   if (((iYear % 4 == 0) && (iYear % 100 != 0)) || (iYear % 400 == 0))
 反例:
   if (iYear % 4 == 0 && iYear % 100 != 0 || iYear % 400 == 0)

3.6.3 规则三

避免表达式中的附加功能,不要编写太复杂的复合表达式。

正例:
aiVar[1] = aiVar[2] + aiVar[3];
aiVar[4]++;
iResult = aiVar[1] + aiVar[4];
aiVar[3]++;
 
反例:   
iResult = (aiVar[1] = aiVar[2] + aiVar[3]++) + ++aiVar[4] ; 

3.6.4 规则四

不可将布尔变量和逻辑表达式直接与TRUE、FALSE或者1、0进行比较。

TURE和FALSE的定义值是和语言环境相关的,且可能会被重定义的。

正例:
    设bFlag 是布尔类型的变量
    if (bFlag)    // 表示flag为真
    if (!bFlag)   // 表示flag为假
反例:
    设bFlag 是布尔类型的变量
    if (bFlag == TRUE)                        if (bFlag == 0) 

在条件判断语句中,当整型变量与0 比较时,不可模仿布尔变量的风格,应当将整型变量用“==”或“!=”直接与0比较。

正例:  if (iValue == 0)  
反例:  if (iValue)    // 会让人误解 iValue是布尔变量

3.6.5 规则五

不可将浮点变量用“==”或“!=”与任何数字比较。

正例:
if ((fResult >= -EPSINON) && (fResult <= EPSINON))
反例:
if (fResult == 0.0)   // 隐含错误的比较

应当将指针变量用“==”或“!=”与NULL比较。

正例:
   if (pHead == NULL)  // pHead与NULL显式比较,强调pHead是指针变量
反例:
   if (pHead == 0)        // 容易让人误解pHead是整型变量
   if (pHead)         // 容易让人误解pHead是布尔变量

3.6.6 规则六

在switch语句中,每一个case分支必须使用break结尾,最后一个分支必须是default分支。

不可在for 循环体内修改循环变量,防止for 循环失去控制。

循环嵌套次数不大于3次。

3.7 函数与过程

3.7.1 规则一

如果函数没有参数,则用void填充。

正例:
void SetValue(int iWidth, int iHeight);
float GetValue(void);
反例:
void SetValue(int, int);
float GetValue();

3.7.2 规则二

如果参数是指针,且仅作输入用,则应在类型前加const。

防止该指针在函数体内被意外修改。

int GetStrLen(const char *pcString);

当结构变量作为参数时,应传送结构的指针而不传送整个结构体,并且不得修改结构中的元素,用作输出时除外。

int GetName(const TComp &comp);

3.7.3 规则三

避免函数有太多的参数,参数个数尽量控制在5个以内。

如果参数太多,在使用时容易将参数类型或顺序搞错,而且调用的时候也不方便。如果参数的确比较多,而且输入的参数相互之间的关系比较紧密,不妨把这些参数定义成一个结构,然后把结构的指针当成参数输入。

参数的顺序要合理。

参数的顺序要遵循程序员的习惯。如输入参数放在前面,输出参数放在后面等。

尽量不要使用类型和数目不确定的参数。

对于参数个数可变的函数调用,编译器不作类型检查和参数检查。这种风格的函数在编译时丧失了严格的类型安全检查。

避免使用BOOLEAN参数。

一方面因为BOOLEAN参数值无意义,TRUE/FALSE的含义是非常模糊的,在调用时很难知道该参数到底传达的是什么意思;其次BOOLEAN参数值不利于扩充。

3.7.4 规则四

不要省略返回值的类型,如果函数没有返回值,那么应声明为void类型。

C语言中,凡不加类型说明的函数,一律自动按整型处理。如果不注明类型,容易被误解为void类型,产生不必要的麻烦。

C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用C函数,为了避免混乱,规定任何C/ C++函数都必须有类型。

对于有返回值的函数,每一个分支都必须有返回值。

为了保证对被调用函数返回值的判断,有返回值的函数中的每一个退出点都需要有返回值。

如果返回值表示函数运行是否正常,规定0为正常退出,不同非0值标识不同异常退出。避免使用TRUE或FALSE作为返回值。

对输入参数的正确性和有效性进行检查。

很多程序错误是由非法参数引起的,我们应该充分理解并正确处理来防止此类错误。

3.7.5 规则五

防止将函数的参数作为工作变量。
将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。之内。

函数体的规模不能太大,尽量控制在200行代码。

必须对所调用函数的错误返回值进行处理。

函数返回错误,往往是因为输入的参数不合法,或者此时系统已经出现了异常。如果不对错误返回值进行必要的处理,会导致错误的扩大,甚至导致系统的崩溃。

减少函数本身或函数间的递归调用。

递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。

对于前台软件为了系统的稳定性和可靠性,往往规定了进程的堆栈大小。如果采用了递归算法,收敛的条件又往往难以确定,很容易使得进程的堆栈溢出,破坏系统的正常运行;另外,由于无法确定递归的次数,降低了系统的稳定性和可靠性。

3.7.6 规则六

设计高扇入、合理扇出的函数

扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它

扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解成多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。

扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。

3.8 可靠性

3.8.1 规则一

在程序编制之前,必须了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。

防止内存操作越界。

内存操作主要是指对数组、指针、内存地址等的操作,内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细。

3.8.2 规则二

必须对动态申请的内存做有效性检查,并进行初始化;动态内存的释放必须和分配成对以防止内存泄漏,释放后内存指针置为NULL。

对嵌入式系统,通常内存是有限的,内存的申请可能会失败,如果不检查就对该指针进行操作,可能出现异常,而且这种异常不是每次都出现,比较难定位。

指针释放后,该指针可能还是指向原有的内存块,可能不是,变成一个野指针,一般用户不会对它再操作,但用户失误情况下对它的操作可能导致程序崩溃。

3.8.3 规则三

不使用realloc( )。

调用realloc对一个内存块进行扩展,导致原来的内容发生了存储位置的变化, realloc函数既要调用free,又要调用malloc。执行时究竟调用哪个函数,取决于是要缩小还是扩大相应内存块的大小。

变量在使用前应初始化,防止未经初始化的变量被引用。

不同的编译系统,定义的变量在初始化前其值是不确定的。有些系统会初始化为0,而有些不是。

3.8.4 规则四

由于内存总量是有限的,软件系统各模块应约束自己的代码,尽量少占用系统内存。

在通信程序中,为了保证高可靠性,一般不使用内存的动态分配。

在往一个内存区连续赋值之前(memset,memcpy…),应确保内存区的大小能够容纳所赋的数据。

尽量使用memmove( )代替memcpy( )。 在源、目的内存区域发生重叠的情况下,如果使用memcpy可能导致重叠区的数据被覆盖。

C++程序中,分配内存使用new和delete,而不使用malloc和free

new和delete操作由编译器决定具体分配和释放内存的大小,相对于malloc和free更为高级

3.8.5 规则五

指针类型变量必须初始化为NULL。

指针不要进行复杂的逻辑或算术操作。指针加一的偏移,通常由指针的类型确定,如果通过复杂的逻辑或算术操作,则指针的位置就很难确定。

如果指针类型明确不会改变,应该强制为const类型的指针,以加强编译器的检查,可以防止不必要的类型转换错误。

减少指针和数据类型的强制类型转化。

3.8.6 规则六

移位操作一定要确定类型。BYTE的移位后还是BYTE,如将4个字节拼成一个long,则应先把字节转化成long

unsigned char ucMove;
unsigned long lMove;
unsigned long lTemp;
ucMove = 0xA3;
lTemp = (unsigned long) ucMove;
lMove = (lTemp << 8) | lTemp;   /* 用4个字节拼成一个长字 */
lMove = (lMove << 16) | lMove;

对变量进行赋值时,必须对其值进行合法性检查,防止越界等现象发生,尤其对全局变量赋值时,应进行合法性检查,以提高代码的可靠性、稳定性。

3.8.7 规则七

类中的属性应声明为private,用公有的函数访问。这样可以防止对类属性的误操作。

class CCount
{
public:
int GetCount(void);
void SetCount(int iCount);
private:
    int m_iCount;

3.8.8 规则八

在编写派生类的赋值函数时,注意不要忘记对基类的成员变量重新赋值。除非在派生类中调用基类的赋值函数,否则基类变量不会自动被赋值。

构造函数应完成简单有效的功能,不应完成复杂的运算和大量的内存管理。如果该类有相当多的初始化工作,应生成专门的Init(…)函数,不能完全在构造函数中进行,因为构造函数没有返回值,不能确定初始化是否成功。

正确处理拷贝构造函数与赋值函数。由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。

3.8.9 规则九

过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出之前要关闭,除非要把这个句柄传递给其它函数使用。

3.9 可测试性

3.9.1 规则一

在同一项目组或产品组内,为准备集成测试和系统联调,要有一套统一的调测开关及相应信息输出函数,并且要有详细的说明。统一的调试接口和输出函数由模块设计和测试人员根据项目特性统一制订,由项目系统人员统一纳入系统设计中

在同一个项目组或产品组内,调测打印出的信息串要有统一的格式。信息串中应当包含所在的模块名(或源文件名)及行号等信息。

3.9.2 规则二

在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码(如打印函数等)。

程序的调试与测试是软件生存周期中非常重要的一个阶段,如何对软件进行较全面、高效率的测试并尽可能地找出软件中的错误就成为非常关键的问题。因此在编写源代码之前,除了要有一套比较完善的测试计划外,还应设计出一系列测试代码作为手段,为单元测试、集成测试及系统联调提供方便。

3.10 断言与错误处理

3.10.1 规则一

整个软件系统应该采用统一的断言。如果系统不提供断言,则应该自己构造一个统一的断言供编程时使用。

整个软件系统提供一个统一的断言函数,如Assert(exp),  同时可提供不同的宏进行定义(可根据具体情况灵活设计),如:

(1)#define ASSERT_EXIT_M  中断当前程序执行,打印中断发生的文件、行号,该宏一般在单调时使用。

(2)#define ASSERT_CONTINUE_M  打印程序发生错误或异常的文件,行号,继续进行后续的操作,该宏一般在联调时使用。

(3)#define ASSERT_OK_M  空操作,程序发生错误情况时,继续进行,可以通过适当的方式通知后台的监控或统计程序,该宏一般在RELEASE版本中使用。

3.10.2 规则二

使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。

指向指针的指针及更多级的指针必须逐级检查。

Assert ( (ptStru != NULL)
 && (ptStru->ptForward != NULL)
 && (ptStru->ptForward->ptBackward != NULL));
反例:
Assert (ptStru->ptForward->ptBackward != NULL);

3.10.3 规则三

对较复杂的断言加上明确的注释。为复杂的断言加注释,可澄清断言含义并减少不必要的误用。

用断言保证没有定义的特性或功能不被使用。

用调测开关来切换软件的DEBUG版和RELEASE版,而不要同时存在RELEASE版本和DEBUG版本的不同源文件,以减少维护的难度。DEBUG版和RELEASE版的源文件相同,通过调测开关来进行区分,有利于版本的管理和维护。

正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉)加快软件运行速度。

在软件系统中设置与取消有关测试手段,不能对软件实现的功能等产生影响。 即有测试代码的软件和关掉测试代码的软件,在功能行为上应该一致。

用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况.,对RELEASE版本不用的测试代码可以通过断言来检查测试代码中的非法情况。

大佬觉得有用的话点个赞 👍🏻 呗。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥任务在无形中完成,价值在无形中升华,让我们一起加油吧!🌙🌙🌙


目录
相关文章
|
7月前
|
算法 程序员 Python
如何写出优美整洁的代码
【4月更文挑战第5天】 编写优美整洁的代码能提升可读性、可维护性和开发效率。遵循命名规范,如使用小写字母和下划线命名变量,驼峰命名法命名函数和类。适当注释代码,但避免过度注释。避免冗余代码,通过函数封装重复逻辑。使用空格和缩进增强代码可读性,遵循PEP 8编码规范。利用异常处理机制处理错误,保持代码简洁。
53 0
|
安全 程序员 C++
代码规范:函数设计
除非告诉人们“危险”是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略,甚至被删除。 ↩︎
91 0
|
安全 Go
Go语言封装艺术:代码安全与结构清晰
Go语言封装艺术:代码安全与结构清晰
83 0
|
5月前
|
测试技术
代码可读性问题之使用代码生成工具帮助我们提升代码可读性,如何解决
代码可读性问题之使用代码生成工具帮助我们提升代码可读性,如何解决
|
7月前
|
存储 缓存 算法
代码简洁之道:我们该如何规范代码的命名?
代码简洁之道:我们该如何规范代码的命名?
120 1
|
7月前
|
前端开发 测试技术
代码注释怎么写:让你的代码更易维护
在编程中,有一种无声的艺术,那就是代码注释。这可能看起来微不足道,但其实非常关键。它不仅有助于他人理解你的代码,也是自我表达的一种方式。
|
人工智能 自然语言处理 Java
提高代码可读性的秘诀:注释的重要性
A:你写代码怎么连注释都不加? B:老大为什么要加注释? A:你不加注释,你怎么知道我能看懂你的代码? B:遇到问题你找到就可以了啊? A:那你哪天生病了请假了闹情绪了离职了,公司怎么办? B:我现在反正没觉得有什么问题,我对公司也很满意,安心啦! 又是00后整顿职场的一段精彩演绎。不可置否,在实际的软件开发过程中,确实有很多开发人员依然不愿意写注释认为这会浪费时间,或者自认为他们的代码足够清晰,不需要额外的解释。但这种想法too young too simple,代码注释对于项目的质量和效率有着深远的影响,在软件开发中的重要性不容小觑。
|
前端开发
案例14-代码结构逻辑混乱,页面设计不美观
代码结构逻辑混乱,页面设计不美观
|
消息中间件 设计模式 JavaScript
如何写出整洁的代码 上
如何写出整洁的代码 上