整洁的代码在团队中无疑是很受欢迎的,能够高效的被其它成员理解和维护,本文参考《C++代码整洁之道》和《Google C++编码规范》,结合本身的一些想法整理以下:
nginx
C++自己做为面向对象语言,首先介绍下面向对象通常涉及到的开发原则。c++
面向对象开发原则
依赖倒置原则:针对接口编程,依赖于抽象而不依赖于具体,抽象(稳定)不该依赖于实现细节(变化),实现细节应该依赖于抽象,由于稳定态若是依赖于变化态则会变成不稳定态。git
开放封闭原则:对扩展开放,对修改关闭,业务需求是不断变化的,当程序须要扩展的时候,不要去修改原来的代码,而要灵活使用抽象和继承,增长程序的扩展性,使易于维护和升级,类、模块、函数等都是能够扩展的,可是不可修改。数据库
单一职责原则:一个类只作一件事,一个类应该仅有一个引发它变化的缘由,而且变化的方向隐含着类的责任。编程
里氏替换原则:子类必须可以替换父类,任何引用基类的地方必须能透明的使用其子类的对象,开放关闭原则的具体实现手段之一。windows
接口隔离原则:接口最小化且完备,尽可能少public来减小对外交互,只把外部须要的方法暴露出来。设计模式
最少知道原则:一个实体应该尽量少的与其余实体发生相互做用。数组
将变化的点进行封装,作好分界,保持一侧变化,一侧稳定,调用侧永远稳定,被调用侧内部能够变化。微信
优先使用组合而非继承,继承为白箱操做,而组合为黑箱,继承某种程度上破坏了封装性,并且父类与子类之间耦合度比较高。网络
针对接口编程,而非针对实现编程,强调接口标准化。
C++开发原则
经过上述面向对象开发原则的理解能够细化到具体C++开发原则。
保持简单和直接原则(KISS, Keep it simple and stupid):保持代码尽量简单,若是需求须要的话,才在代码中引入灵活的可变点,只添加那些可以使总体变得更简单的局部复杂的东西。
不须要原则(YAGNI, You're not gonna need it):老是在你真正须要的时候再实现他们,而不是在你只是预见到你未来会须要他们而去实现,在真正须要的时候再写代码,那时再重构也来得及。
避免复制原则(DRY, Do not repeat yourself):不要复制,不要重复,这是至关危险的操做,你修改一处代码的时候总能记得去修改另一处或另外多处你曾经复制的代码吗?
信息隐藏原则:一段代码调用了另一段代码,调用者不该该知道被调用者代码的实现,不然调用者就有可能修改被调用者的实现来实现某些功能,而这有可能引起其它调用者的bug。
高内聚低耦合原则:相似单一职责原则,明确每一个模块的具体责任,尽可能少的依赖于其它模块。
最少惊讶原则:函数功能要与函数名字功能一致,难道你要在一个getter()函数去更改为员变量的值吗?
更干净原则(自命名):离开露营地的时候,应让露营地比你来以前还要干净,当发现代码中有须要改进或者风格很差的地方,应该马上改掉,不要care这段代码的原做者是谁,也不要care这是谁的模块,代码全部权是集体的,每一个团队成员在任什么时候候都应该能够对任何代码进行更改和扩展。
关于面向对象设计原则能够参考一文让你搞懂设计模式
注重单元测试
重要性就很少说了,防患于未然,构建大型系统尤为须要进行单元测试,保证代码质量,能够防患于未然。通常都讲究测试驱动开发,开发一个功能首先要想好怎么测试,先把测试代码写好,再去开发对应的需求。经过单元测试也有利于开发者更好的进行接口的设计,主要说下良好的单元测试的原则。
单元测试的原则
保证单元测试的代码的质量,单元测试的代码也是代码,不该该和产品代码区别对待,并且单元测试的代码再写出bug更影响测试效率。
单元测试的命名, 每一个测试单元须要根据具体测试内容进行相应的命名,方便定位分析问题,好的命名若是出现问题时经过测试单元的名字基本就能够定位问题。
保证单元测试的独立性,每一个测试单元都是独立的,不依赖于其它测试单元,不要构建测试单元的上下文,上面的测试单元出问题影响到下面的单元测试的设计是很不友好的。
尽可能保证一个测试单元使用一个断言,保证测试单元内部的一个相对独立性,上面的断言阻碍了下面的断言测试也是很差的设计。
保证单元测试环境的独立,保证每一个测试单元都有独立的环境,不依赖于其它环境,每一个测试单元都要是个独立的可运行的实例,每一个单元测试结束后记得清理环境。
不必对第三方库和外部系统作单元测试,只对本身写的代码进行测试。
单元测试尽可能不要涉及数据库,数据库的状态是全局的,测试不能保证独立性,并且数据库的访问也是缓慢的,影响单元测试的速度,若是真的须要能够模拟数据库在内容中进行测试,其实一般是在系统集成和系统测试级别时去测试数据库。
不要混淆测试代码和产品代码,产品代码中不该依赖测试代码。
测试必需要快速执行,确保秒级别,大型系统的单元测试也就几分钟而已,单元测试不要访问数据库、磁盘、网络等外设。
找一些测试替身,例若有些数据须要经过网络获取,那能够利用依赖注入作一个网络替身的类模拟这些数据的产生,能够研究研究Google mock。
良好的命名
不管是什么语言,函数和变量的良好命名都是颇有必要的,经过函数的名字咱们就能够知道这个函数里代码的做用,而不是经过写注释,我的一直倾向于用代码自解释。
文件命名
文件名字要所有小写,中间用_相连,后缀名为.cc和.h
类型命名
类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.
变量命名
不要将变量的类型在名字中体现,这样之后变量类型改变的话还须要去改动变量名,充分利用IDE的功能,变量 (包括函数参数) 和数据成员名一概小写, 单词之间用下划线链接. 类的成员变量如下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.
class TableInfo {...private:string table_name_; // 好 - 后加下划线.string tablename_; // 好.static Pool<TableInfo>* pool_; // 好.int i_table; // 很差,不要将变量的类型在名字中体现};
常量命名
声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合
const int kDaysInAWeek = 7;
函数命名
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().
枚举命名
和常量一致
enum UrlTableErrors { kOK = 0, kErrorOutOfMemory, kErrorMalformedInput,};
Tip:
除非像swap函数里tmp那种一目了然,不然不要搞无心义的命名,函数名变量名字宁肯特别长也要写清楚到底是什么意思,不要用缩写,一个变量尽可能在临近使用前才定义,可读性强也可更好利用cpu cache。
编辑器
团队能够统一使用相同的编辑器,我的目前使用的是VS Code编辑器,同时每一个项目使用统一的.clang_format文件,统一规范代码格式,全部的换行符都要用LF格式,不要用CRLF格式,在右下角能够设置。
我的的.clang-format文件以下,是在google风格的基础上作了些修改:
BasedOnStyle: GoogleIndentWidth: 4ColumnLimit: 120SortIncludes: trueMaxEmptyLinesToKeep: 2
C++编码规范要点小总结
每一个头文件都要使用#define避免被重复引用
命名格式 <PROJECT>_<PATH>_<FILE>_H_
...
或使用#pragma once,而#define方式更通用
鼓励在 .cc 文件内使用匿名命名空间或 static 声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径. 禁止使用 using 指示, 禁止使用内联命名空间(inline namespace)
一行尽可能不要超过120个字符,一个函数尽可能不要超过40行,同时一个文件尽可能控制在500行内.
全部的引用形参如不作改动一概加const,在任何可能的状况下都要使用 const或constexpr
new内存的地方尽可能使用智能指针,c++11 就尽可能用std::unique_ptr替代std::auto_ptr
合理使用移动语义,减小内存拷贝,参考左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里
禁止使用 RTTI,尽可能在编译期间就肯定参数类型,不要搞运行时识别typeid这种代码
使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式
明确使用前置++仍是后置++的具体含义,如不考虑返回值,尽可能使用效率高的前置++ (++i)
不要使用uint类型,若是须要使用大整型能够考虑int64,不然类型的隐式类型转换会带来不少麻烦
如无特殊必要不要使用宏,能够考虑使用const或constexpr替代宏,宏的全局做用域很麻烦,若是非要用在立刻要使用时才进行 #define, 使用后要当即 #undef
google文档说必定不要用宏来控制条件编译(可是我本身尚未查到不用宏如何控制条件编译,或许就不要搞条件编译)
尽量用 sizeof(varname) 代替 sizeof(type).使用 sizeof(varname) 是由于当代码中变量类型改变时会自动更新. 您或许会用 sizeof(type) 处理不涉及任何变量的代码,好比处理来自外部或内部的数据格式,这时用变量就不合适了
类型名若是过长的话能够考虑使用auto关键字
注释统一使用 // ,不要经过注释禁用代码,擅用git,不要为易懂的代码写注释
写完代码后记得format,VS Code(windows快捷键) shift + alt + F ,每一个项目最好都有统一的.clang_format文件
使用C++的string和stream替代C语言风格的char*,使用std::ostream和std::cout替代printf()、sprintf()等
尽可能使用STL标准库的容器而不是C语言风格的数组,数组的越界访问之类当时是不会报错的,反而可能弄脏堆栈信息,致使奇奇怪怪难以排查的bug
能够更多的使用模板元编程,尽可能多的使用constexpr等编译器计算,编译器是咱们的好搭档,我的认为模板元编程之后会是C++的主流技术
能够考虑更多的使用异常处理方式,而不是C语言风格的errno错误码等,这里能够参考你的c++团队还在禁用异常处理吗?
附:本文不是技术文章,介绍较为主观,可能和不少人想法有所冲突,各位能够结合本身的经历经验酌情参考。
参考资料
《C++代码整洁之道》
https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/contents/
本文分享自微信公众号 - C语言与CPP编程(cwdushu)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。