软件编码规范文档.docx
- 文档编号:23976230
- 上传时间:2023-05-23
- 格式:DOCX
- 页数:22
- 大小:27.08KB
软件编码规范文档.docx
《软件编码规范文档.docx》由会员分享,可在线阅读,更多相关《软件编码规范文档.docx(22页珍藏版)》请在冰豆网上搜索。
软件编码规范文档
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 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++注释,每行开始加一个额外的斜杠。 注释块可以加在代码对象的上面或者右方,如果加在右方,还需要加额外的<符号,只有成员和参数的注释可以加在右边。 一般使用简要注释即可,如需要更详细的说明的可以使
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 软件 编码 规范 文档
![提示](https://static.bdocx.com/images/bang_tan.gif)