用c++开发一款商业产品,需要哪些规范

浏览: 63 发布日期: 2016-12-01 分类: windows

最近开始接手项目组的开发管理工作,项目组开发的产品一期功能基本开发完成,进入内部测试及小渠道发布阶段,然而产品的稳定性还存在很大问题。

先做一下背景介绍,项目组开发的是一款面向C端的互联刚产品,运行操作系统为windows。整个项目使用c++开发,总体代码量大概在数十万行。

c++是一门很复杂的语言,有很多强大的特性,然而当用其开发一款商业产品时,这些特性可能会带来麻烦。所以当设计c++的使用规范时,更多的是对其做减法。

本文的规范针对VC++开发环境,开发工具为Visual Studio。

文件系统目录规范

一款完整的商业产品开发通常会涉及到很多模块,这其中包括可执行程序(.exe),项目组开发的库(静态库或动态库),第三方的库(静态库或动态库),测试程序,这么多的模块和代码,需要一个良好组织的目录结够。
这里假设项目名称为XXProject

  • XXProject

    • XXProject.sln

    • Bin

      • Debug

      • Release

    • TestBin

      • Debug

      • Release

    • Src

    • Document

其中Bin存放需要发布的可执行程序,TestBin存放测试程序的可执行文件,Src存放项目的工程文件和源代码,Document存放项目相关的开发文档(如项目说明,代码规范等)。

接下来假设项目包括如下工程:XXMain(发布的主程序),XXUpdate(升级程序),XXSdk(自己开发的基础库),XXThird(第三方库), XXTest(测试程序)

  • Src

    • XXMain

      • XXMain.vcxproy

      • code文件

    • XXUpdate

      • XXUpdate.vcxproy

      • code文件

    • XXTest

      • XXTest.vcxproy

      • code文件

    • XXSdk

      • XXSdk.vcxproy

      • code文件

    • ThirdLib

      • XXThird

    • Lib

      • Debug

      • Release

    • Include
      Lib库用来存放静态库,动态库的.a文件,Include用来存放公共头文件,ThirdLib用来存放第三方库。

解决方案目录规范

解决方案目录是VS开发工具提供的逻辑上组织项目的方式,与物理文件系统并不存在对应关系。
仍然假设项目包含上述项目和模块。

  • Solution (解决方案)

    • Application (解决方案文件夹)

      • XXMain (工程)

      • XXUpdate (工程)

    • Test (解决方案文件夹)

      • XXTest (工程)

    • Library (解决方案文件夹)

      • XXSdk

    • ThirdLibrary(解决方案文件夹)

      • XXThird

    • Public (解决方案文件夹)

      • 公共头文件

代码编写规范

1:禁用全局变量

全局变量会带来晦涩的依赖问题

2:禁用goto指令

goto指令的代码难以阅读和维护

3:禁用异常机制

c++的异常机制有很多缺陷且复杂

4:用struct封装数据,使用class定义对象

C++中class和struct几乎没有区别,在规范中进行语义的区分

5:struct和class必须显示包含构造函数
6:除非特殊情况,总是将析构函数定义为虚函数

方便继承时的资源释放

7:不要在构造函数中执行复杂操作,推荐加入init函数用于初始化操作

构造函数没有返回值,难以反馈错误

8:不要在构造函数中调用虚函数

构造函数中的虚函数不会重定向到子类。

9:慎用继承

相比对象组合,继承带来更强的依赖,推荐使用接口继承而不是对象继承

10:禁用多重继承(接口继承除外)

多重继承通常代表不好的设计

11:慎用运算符重载

运算符重载会混淆代码的语义,应只在不会造成混淆时使用

12:将成员设置为私有并提供访问函数

封装是降低代码耦合的有力武器

13:将同一访问权限的成员定义在一起

可以按照public,protect,private顺序进行组织

14:避免出现大而全的类

当一个类的代码超过1000行,应有所警惕,超过2000行,则应考虑拆分(行数不包括注释)

15:头文件应包含它所需要的头文件

这样可以保证cpp文件引入该头文件后不需要包含其它头文件

16:合理的组织引入的头文件,不要重复引入,不要引入不必要的头文件

可以以系统头文件,第三方库头文件,项目组库头文件,本程序头文件来组合,不同类型头文件之间用空格隔开

17:头文件使用#define宏来避免多重包含

pragma once 指令只有VC编译器能识别

18:允许合理的使用友元特性

19:使用引用传递对象类型参数, 对于不需要改变的参数加入const修饰符

引用传递可以避免对象拷贝

20:函数应该进参在前,出参在后

21:使用明确的返回值指示函数的运行结果,而不是用返回的内容来指示结果

推荐: int GetDeviceName(string& deviceName);
不推荐: string GetDeviceName()

22:声明基本类型变量后立即赋值

正确 int nCount = 0; bool bSuc = false; 错误 int nCount; bool bsuc;

23:使用内联,枚举,常量来代替宏

宏的使用有很多弊端,应尽量避免

24:使用singleton模式代替静态类

相比静态类,singleton模式可以更好地控制初始化时机。

25:使用share_ptr来管理指针

指针和管理在复杂项目中十分困难,使用智能指针是不二选择

26:使用weak_ptr来处理循环引用
27:明确对象或资源的生存周期

明确对象的生存周期通常代表着良好的设计

28:合理的使用缩进,空格

最重要的是保持风格的统一,自动生成的代码可能会打破这种统一,应该灵活设计规则

29:合理使用typedef缩减类型的长度,合理使用auto

使用stl时经常会导致过长的类型,合理使用auto可以有效减少代码长度

命名规则

由于是在VC环境下开发,沿用微软的命名驼峰命名法

选择哪种命名方式实际上不是很重要,最重要的是保持统一

  • 代码文件: DeviceMgr.h, DeviceMgr.cpp

  • 解决方案目录: NetLibrary

  • 工程筛选器: DataModel

  • 类:CDeviceMgr

  • 结构体: DeviceInfo

  • 变量: listDevice

  • 类成员: m_deviceName

  • 函数: GetName; GetDeviceName;

  • 代表bool含义的变量: 都以b开头,如:bOk, bSafe

  • 代表整数含义的变量: i表示符号整数,n表示无符号整数

  • 避免无意义的变量名和缩写: 如 x,dn(deviceName)等

注释规则

  • 文件注释:注明文件作者,联系方式,文件代码作用,重大修改记录等

  • 类注释:说明类的作用,使用限制等等

  • 函数注释: 尽量依靠意义明确的函数命名而不是依靠注释,说明函数的使用限制,对于意义不明确的参数加以说明

  • 变量注释:尽量依靠意义明确的变量命名而不是依靠注释,特殊情况。

  • 实现注释: 对于使用了非常规技巧,或复杂算法,或很复杂的业务逻辑部分要加入注释说明

原则: 注释应风格统一,简短而意义明确,最终目的是有效帮助其它人阅读和理解代码的目的

日志

日志的打印十分重要,是产品发生问题时重要的参考依据

  • 日志的打印要尽量详尽,合理划分日志等级,一般为Fatal, ERROR, WARNING, DEBUG, INFO

  • 统一使用unicode(utf-16)编码来输出日志

  • 提供异步打印日志的接口

  • 提供定期清理日志的机制

  • 在发布版中将日志等级设置为ERROR或更高,提供配置文件供调整日志等级

一些其它规则

  • 避免大而全的类(代码控制在1000行以内)

  • 避免过长的函数(代码控制在200行以内)

  • 避免深层的嵌套(不要超过3层)

  • 使用do-while-break技巧来避免重复写释放资源的代码

  • 尽量使用RALL技巧来释放资源

  • 当函数或代码废弃时,应与标注,最好将其注释掉并定期清理

线程和锁

复杂的项目肯定会涉及到多线程开发,而开发多线程程序是十分困难的。据我们统计,项目组产品有70%左右的崩溃和bug和线程有关。

将线程模块独立出来,交给项目最有经验的开发人员管理和维护,对外暴露抽象接口,屏蔽线程的概念。

强制使用RALL技术来使用锁

未尽
本文并没有涉及到C++规范的所有方面,欢迎讨论和补充

返回顶部