软件编码规范文档.docx

上传人:b****8 文档编号:23976230 上传时间:2023-05-23 格式:DOCX 页数:22 大小:27.08KB
下载 相关 举报
软件编码规范文档.docx_第1页
第1页 / 共22页
软件编码规范文档.docx_第2页
第2页 / 共22页
软件编码规范文档.docx_第3页
第3页 / 共22页
软件编码规范文档.docx_第4页
第4页 / 共22页
软件编码规范文档.docx_第5页
第5页 / 共22页
点击查看更多>>
下载资源
资源描述

软件编码规范文档.docx

《软件编码规范文档.docx》由会员分享,可在线阅读,更多相关《软件编码规范文档.docx(22页珍藏版)》请在冰豆网上搜索。

软件编码规范文档.docx

软件编码规范文档

 

C++设计编码规范

V1.00(试用版)

设备开发部黄焕斌

重要提示

本规范中的示例代码都在表格框中显示,绿色的表格框表示正确的示例代码,红色的表格框表示不建议的示例代码。

背景

C++是大华设备软件和平台软件开发的主要软件,在新的软件框架里,两种平台的组件甚至是共用的。

统一的代码风格,良好的设计风格,有利于代码的实现和阅读,有利于减少代码错误和提高代码效率,能有效地促进技术的交流和发展。

常见的代码规范都异常冗长,调调框框太多。

本规范力求以简明的内容,概括一些重要的规则,将相似的规则进行提炼集中描述,并提供对照的示例代码加深理解。

规范的使用者花半个小时左右,就可以熟悉整个规范。

所有大华基于新软件框架的底层组件,业务组件,应用组件都必须遵守此规范。

例外

本规范是强制要求,不过有些情况例外:

与第三方库有关的代码:

比如stl,boost,json等等,使用、移植这些库时,相关的代码可以按照这些库的规范。

Windows代码:

主要指基于公共软件框架,同时使用了非公共组件框架内的其他API接口的组件,可以继续保留Windows的规范。

1文件组织规则

1.1命名

所有的目录和文件名使用大写字母开头的单词组合,目录名单词之间可以用空格分开。

引用文件名时要严格区分大小写。

与操作系统关系密切的工程的命名可以参考操作系统的规则。

Timer.cpp//源文件

Timer.h//头文件

Font.bin//资源文件

Config1//配置文件

1.2目录

一个大的工程是由多个组件或模块组成的,对于每个组件或模块,其代码应集中管理,并具备完整的设计文档和单元测试代码,用子目录分类存放。

目录或文件

说明

Bin

测试程序目录

Doc

设计文档目录

Include

依赖的组件头文件目录与本组件的外部接口头文件目录,映射到其他地址

Lib

不同平台生成的库和依赖的库文件目录

Makefile.Configs

Makefile的不同平台的配置文件目录

Src

源文件,内部头文件

Test

单元测试代码

Makefile

allRules.mk

Makefile文件,一次性编译Makefile.Configs目录下所有配置对应的库,测试程序

1.3预处理

为了防止头文件被重复引用,使用ifndef/define/endif结构产生预处理块。

预处理宏中的单词应与文件名基本一致。

//文件名为Guard.h

#ifndef__GUARD_H__

#define__GUARD_H__

//Guard类的定义...

#endif//__GUARD_H__

1.4注释

源文件和头文件的头部都应进行注释,列出svn文件ID,版权申明,文件描述(说明是什么模块或什么类对应的文件),修改记录(修改时间、svn作者、修改内容),可使用va模板。

对于新加入svn的文件,应该其文件属性的svn属性列表中加入(svn:

keywords,Id)属性。

不要和文件中的类或模块的注释混淆。

//

//"$Id$“

//

//Copyright(c)1992-2007,ZheJiangDahuaTechnologyStockCO.LTD.

//AllRightsReserved.

//

//Description:

//Revisions:

Year-Month-DaySVN-AuthorModification

//

2代码组织规则

2.1空行

类、结构、联合、函数、枚举等定义结束后,应加空行。

类定义内部相关的成员变量或操作之间不加空行,其他地方应加空行。

函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行。

源文件和头文件末尾保留一个空行。

classA

{

};

classB;

2.2换行

每行代码只写一条语句。

拆分复杂的复合表达式。

代码行长度控制在80左右。

长表达式在低优先级操作符处拆分成新行,操作符放在新行之首。

if(loadFile(m_customFilePath.c_str(),m_stream)

&&reader.parse(m_stream,m_configAll)

&&m_configAll["Groups"].size()>=1

&&m_configAll["Users"].size()>=1)

{

infof("CUserManager:

:

SetDefault()applycustomconfig.\n");

}

2.3空格

‘,’之后要留空格。

如果‘;’不是一行的结束符号,其后要留空格。

二目或三目操作符前后留空格。

但“[]”、“.”、“->”这类操作符和作用于分辨符“:

:

”前后不加空格。

修饰符‘*’和‘&’紧靠变量名,仅在前面加空格。

char*name;

int*x,y;//此处y不会被误解为指针

for(inti=0;i

{

}

2.4对齐

程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。

{}之内的代码块使用TAB缩进并对齐,换行后的代码块使用TAB缩进并对齐。

代码前的注释应和所注释的代码对齐。

//commentoffoo

voidfoo()

{

dosomething

}

2.5就近原则

较为紧密的代码应尽可能相邻。

变量应在在定义的同时初始化。

C++函数中将局部变量的定义放在要使用它的代码前最近处。

将类的public接口申明放在类定义的最前面。

voidfoo()

{

inta=0;

a++;

intb=0;

b++;

}

2.6精简原则

DRY(Don'tRepeatYourself)系统中的每一项知识都需具有单一的表示。

KISS(KeepItSimpleStupid)保持尽量简单。

类中多次使用定义的很长的类型,应在类定义中对该类型进行更简洁自定义。

访问一个变量的表达式太长,应定义一个临时的指针或引用来替换它。

函数中多次调用返回结果不变的其他函数,应使用临时变量保存结果。

函数中多次引用单件,应使用临时变量甚至类的成员变量来保存单件的引用或指针。

将if/else/return的组合改为return条件表达式组合。

Json:

:

Value&users=m_configAll["Users"];

for(uinti=pos;i

{

users[i]=users[i+1];

}

users.resize(users.size()-1);

return(condition?

x:

y);

3命名规则

整体上采用Java的命名规则,提高拼写效率。

3.1自注释

标识符应是有意义的单词或其组合。

用约定俗成的词,除了通用的缩写,应使用全拼。

拼写要正确,注意单复数,禁止使用拼音。

用最短的长度表达最准确的信息。

//smellyexample

typedefstructtagPICINFO_INJPG

{

 char jpglog[4];   //

 int    type;        //类型0:

通行1:

超速(暂无效)

 char   sbid[10];    //设备ID

 int    xscd;        //形行驶ID

 char   cphm[20];    //车牌号码

 int    cplx;        //车牌类型

 int    cpys;        //车牌颜色

 char   tgsj[19];    //抓拍时间

}PICINFO_INJPG;

3.2变量

使用“名词”或者“形容词+名词”,首个单词首字母小写开头,其他单词大写打头。

全局变量加前缀“g_”。

成员变量加前缀“m_”,结构和联合的成员变量不用加。

静态变量加前缀“s_”,静态成员变量加“sm_”。

floatvalue;

floatoldValue;

floatnewValue;

3.3函数

使用“动词”或者“动词+名词”(动宾词组),名词前可再加修饰词,首个单词首字母小写开头,其他单词大写打头。

C-API函数最前面应该是是小写的模块名。

类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。

voidsleep();//全局函

intsetValue(…);//全局函数

intgetValue(…);//全局函数

voidglCallList(GLuintlist);//OpenGLAPI

box->draw();//成员函数

3.4类型

可直接使用bool,char等内建类型,其他使用stdint.h定义的类型。

算术自定义类型,类,结构,联合,枚举类型统一使用大写字母开头的单词组合。

class类型的命名前加大写字母’C’;

templateclass类型的命名前加大写字母’T’;

接口类的命名前加大写字母’I’。

在类中嵌套的定义有具体意义的类型,是代码更易读。

typedefintDistance;

unionAddress;

enumMode;

structUser;

classCPacket;

template

:

allocator>classTCircularQueue;

classIDevVideoEnc;

3.5宏

全部使用大写字母,单词之间用下划线分开。

给代码控制宏命名加上前缀,来分类不同用途的宏。

以上都是指自定义的宏,不包括编译预定义宏。

#defineJSON_VALUE_USE_INTERNAL_MAP1

#defineJSON_USE_CPPTL1

3.6常量

避免使用常量宏,不得不使用时,参考宏的命名规则。

const,枚举值等常量,应尽量作为类的成员,命名方式和变量相同。

#defineMAX_SIZE10//notsogood

classA

{

enum

{

maxSize=10

};

}

4设计规则

4.1识别类和函数

基本原则是高抽象性,高内聚性,低耦合性。

使类的接口完整并且最小。

一个函数不要完成多个功能。

一个变量也不能有多用途。

把方法中的重复代码抽象成私有函数。

尽量使用已有的函数或者标准库来实现新的函数。

区分两个类“A是一个B”与“A是B的一部分”的关系,分别对应类的继承和聚合关系。

//正确的设计,虽然代码冗长。

 

classHead

 {

 public:

   voidLook(void){m_eye.Look();}

   voidSmell(void){m_nose.Smell();}

   voidEat(void){m_mouth.Eat();}

   voidListen(void){m_ear.Listen();}

 private:

   Eyem_eye;

   Nosem_nose;

   Mouthm_mouth;

   Earm_ear;

};

如果允许Head从Eye、Nose、Mouth、Ear派生而成,那么Head将自动具有Look、Smell、Eat、Listen这些功能。

示例十分简短并且运行正确,但是这种设计方法却是不对的。

//功能正确并且代码简洁,但是设计方法不对。

classHead:

publicEye,publicNose,publicMouth,publicEar

{

};

4.2构造函数

如果一个类可能有多个构造函数,应有公用的私有初始化函数对成员进行初始化。

如果确定只有一个构造函数,应使用成员初始化列表进行初始化。

并且初始化列表中成员列出的顺序和它们在类中声明的顺序相同。

使用explicit关键字消除隐式转换

classCString

{

//…

explicitCString(intn);//preallocatenbytes

CString(constchar*p);

};

CStrings1=‘a’;//error:

noimplicitchar->CStringconversion

CStrings2(10);//ok:

stringwithspacefor10characters

将采用了单件实例的类或接口的构造函数和析构函数权限设置为private或protected

classITimerManager

{

public:

staticITimerManager*instance();

protected:

ITimerManager();

virtual~ITimerManager();

4.3析构函数

确定基类有虚析构函数。

4.4拷贝函数与赋值函数

为非POD类型(plain-old-data,onlyints,chars,floats,orpointers,orarrays/structsofPOD)类声明一个拷贝构造函数和一个赋值操作符。

而且在operator=中,应返回*this的引用,对所有数据成员赋值,检查给自己赋值的情况。

classCZString

{

public:

CZString(constchar*cstr);

CZString(constCZString&other);

~CZString();

CZString&operator=(constCZString&other)

{

if(this==&other)return*this;

CZStringtemp(other);

constchar*cstr=m_cstr;

m_cstr=temp.m_cstr;

temp.m_cstr=cstr;

}

private:

constchar*m_cstr;

}

4.5封装性

不要重新定义继承而来的非虚函数、成员、函数的缺省参数值。

避免出现public数据成员。

单件模式对象构造函数应为私有类型。

如果一个操作不会修改对象的属性,应该加const修饰,妥善处理const的传递性。

将只与类有关的常量、类型的定义放在类的内部。

classA

{

public:

intgetValue()const;

private:

intvalue;

classInfo

{

};

}

4.6函数参数

如果参数是一个对象,应改成传递其引用或者指针。

如果参数是指针或引用,且仅作输入用,则应在类型前加const,注意值传递不用修饰。

sizt_tstrlen(constchar*string);

CRect(constCPoint&point,constCSize&size);

不能计算指针形参对应的实参数组的大小(已退化)。

函数的参数顺序应该是先输入参数,再是输出参数,同时数组的地址在前,长度在后。

4.7函数返回值

不要返回栈对象的指针或引用,不要返回栈数组。

Object*createObject()

{

Objectobject;

return&object;

}

设计函数时,必须返回一个对象时不要试图返回一个引用。

Object&createObject()

{

Objectobject;

returnobject;

}

Object&createObject()

{

Object*object=newObject;

return*object;

}

4.8契约

提供约束,宁可编译和链接时出错,也不要运行时出错。

检查函数的前置条件,满足前置条件是调用者的责任,而被调用者假定它的前置条件已经满足。

检查函数的后置条件,也就是函数返回之时哪些条件是调用者可以期望的。

检查类的不变式,类的不变式确保类处于良好的状态中,一般提供一个成员函数如isValid()在函数进入和退出时检查不变式。

对于运行契约检查,一般使用assert,也可以采用日志记录,抛出异常等方式。

///获取队首元素

constT&front()

{

assert(m_size>0);

returnm_queue[m_front];

}

不要混淆运行契约与有效代码,运行契约仅检查参数的合法性,且不应修改契约外定义的变量。

assert(--m_count>0);

4.9规模

函数参数个数尽量控制在5个之内。

函数代码尽量控制在200行代码之内。

每个类的平均方法数尽量控制在20个之内。

函数嵌套深度控制在6级之内,减少没必要的递归嵌套。

函数(调度函数除外)扇出控制在5个以内。

4.10类型转换

避免强制类型转换。

避免隐藏类型转换。

4.11常量

C++中使用const来定义常量,替换宏定义的常量。

不能使用无意义的立即数。

类中使用的常量应使用类的内部定义的枚举类型的值。

只能使用ascii字符的字符串常量,不能使用中文等特定语言的字符串常量。

4.12内联

C++中应该用内联函数替换宏定义的代码段。

对性能要求比较高的场合应使用内联函数。

使用C++标准库常用的内联函数,比如max,min等。

如果使用某函数的地方较多,而且函数体较大,不应使用内联函数。

4.13解耦合

解耦合设计到很多方面,包括模块的划分,头文件依赖,模块对外的接口必须有解耦合处理,隐藏内部实现的细节,避免编译依赖。

如果可以使用对象的引用和指针,就要避免使用对象本身。

定义某个类型的引用和指针只会涉及到这个类型的声明。

定义此类型的对象则需要类型定义的参与。

尽可能使用类的声明,而不使用类的定义。

因为在声明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使函数是通过传值来传递和返回这个类。

不要在头文件中再包含其它头文件,除非缺少了它们就不能编译。

相反,要一个一个地声明所需要的类,让使用这个头文件的用户自己去包含其它的头文件,以使用户代码最终得以通过编译。

使用句柄类(Handleclass)隐藏实现细节来实现解耦合。

//编译器还是要知道这些类型名,因为Person的构造函数要用到它们

classDate;

classAddress;

classPersonImpl;

classPerson

{

public:

 Person(conststring&name,constDate&birthday,constAddress&addr);

 virtual~Person();

 stringname()const;

 stringbirthDate()const;

 stringaddress()const;

private:

 PersonImpl*impl;                //指向具体的实现类

};

除了句柄类,另一选择是使Person成为一种特殊类型的抽象基类,称为协议类(Protocolclass)。

和句柄类的用户一样,协议类的用户只是在类的接口被修改的情况下才需要重新编译。

classPerson

{

public:

 virtual~Person();

 virtualstringname()const=0;

 virtualstringbirthDate()const=0;

 virtualstringaddress()const=0;

 virtualstringnationality()const=0;

};

4.14可重入

对于可能被多个任务访问的资源,要使用互斥量保护,上层应用不应直接使用中断。

保护的范围要准确,一般使用不同的互斥量来保护不同的资源,如果整体的代价不高,也可以使用同一个互斥量。

仔细考察资源之间依赖的情况,防止死锁。

使用Guard(守卫者)来实现函数内的保护,除非难以使用。

通过逻辑的设计,能够保证多个线程访问同一资源不会发生冲突时,可以不保护。

对于只需要自加和自减的变量可以使用AtomicCount来保证原子性。

5内存管理规则

5.1模块化

使用或者设计专有的内存管理模块,而不是直接使用new/delete。

给标准容器编写高效安全的allocator。

使用智能指针管理内存或对象生命周期。

5.2静态分配

在程序启动时从系统中静态分配好需要持续使用的内存,提高性能。

5.3new/delete

C++中使用new/delete替换malloc/free。

如果重载了operatornew就要同时重载operatordelete。

5.4有效性

使用有效的指针及其所指向的空间,杜绝野指针。

5.5正确释放

明确内存块的所属对象及对象的生命周期,及时释放,防止内存泄露。

new出来的数组释放时也应表明它是数组。

5.6拷贝

数组拷贝,或调用内存拷贝、字符串拷贝接口时,应注意源区域和目标区域重叠的情况,参考memmove函数。

6注释规则

6.1有效性

注释的内容要清楚、明了,含义准确,防止注释二义性。

在代码的功能、意图层次上进行注释,提供有用、额外的信息。

对于已经充分自注释程度的代码无需注释。

6.2普通注释

函数内部使用C++注释风格,并在注释符号和注释内容间留一个空格。

注释内容加在代码对象的上方或右方。

if、for、do、while、case、switch、default等语句应该注释。

未break的case段后面应该注释。

代码块、预处理块较长的,应该注释。

6.3Doxygen注释

函数外部使用DoxygenC++风格注释,并在注释符号和注释内容间留一个空格。

注释由简要注释和详细注释组成,两者都是可选的,且是两者都不能重复。

简要注释使用一行C++注释,并在开始加一个额外的斜杠,使用\brief命令可以支持多行,简要注释的范围将以空行或其他命令结束。

详细注释使用至少两行C++注释,每行开始加一个额外的斜杠。

注释块可以加在代码对象的上面或者右方,如果加在右方,还需要加额外的<符号,只有成员和参数的注释可以加在右边。

一般使用简要注释即可,如需要更详细的说明的可以使

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1