原则
好代码的原则
我们参考Kent Beck的简单设计四原则来指导我们的如何写出优秀的代码,如何有效地判断我们的代码是优秀的。
- 通过所有测试(Passes its tests)
- 尽可能消除重复 (Minimizes duplication)
- 尽可能清晰表达 (Maximizes clarity)
- 更少代码元素 (Has fewer elements)
- 以上四个原则的重要程度依次降低。这组定义被称做简单设计原则。第一条强调的是外部需求,这是代码实现最重要的;第二点就是代码的模块架构设计,保证代码的正交性,保证代码更容易修改;第三点是代码的可阅读性,保证代码是容易阅读的;最后一点才是保证代码是简洁的,在简洁和表达力之间,我们更看重表达力。
类和函数设计指导原则
C++是典型的面向对象编程语言,软件工程界已经有很多OOP原则来指导我们编写大规模的,高可扩展的,可维护性的代码:
- 高内聚,低耦合的基本原则
- SOLID原则
- 迪米特法则
- “Tell,Don’t ask”原则
- 组合/聚合复用原则
保证静态类型安全
我们希望C++应该是静态类型安全的,这样可以减少运行时的错误,提高代码的健壮性。但是由于C++的下面的特性存在,会破坏C++静态类型安全,我们针对这部分特性要仔细处理。
- unions联合体
- 类型转换cast
- 缩窄转换narrowing conversions
- 类型退化type decay
- 范围错误range errors
void*
类型指针我们可以通过约束这些特性的使用,或者使用C++的新特性,比如variant(C++17),GSL的span,narrow_cast等来解决这些问题,提高C++代码的健壮性。
遵循C++ISO标准
希望通过使用ISO C++标准的特性来编写C++代码,对于ISO标准中未定义的或者编译器实现的特性要谨慎使用,对于GCC等编译器的提供的扩展特性也需要谨慎使用,这些特性会导致代码的可移植性比较差。
注意:如果模块中需要使用相关的扩展特性来,那么尽可能将这些特性封装成独立的接口,并且可以通过编译选项关闭或者编译这些特性。对于这些扩展特性的使用,请模块制定特性编程指南来指导这些特性的使用。
优先编译时检查错误
通过编译器来优先保证代码健壮性,而不是通过编写错误处理代码来处理编译就可以发现的异常,比如:
- 通过const来保证数据的不变性,防止数据被无意修改。
- 通过gsl::span等来保证char数组不越界,而不是通过运行时的length检查。
- 通过static_assert来进行编译时检查。
使用命名空间来限定作用域
全局变量,全局常量和全局类型定义由于都属于全局作用域,在项目中,使用第三方库中容易出现冲突。
命名空间将作用域细分为独立的,具名的作用域,可有效地防止全局作用域的命名冲突。
- class,struct等都具有自己的类作用域。
- 具名的namespace可以实现类作用域更上层的作用域。
- 匿名namespace和static可以实现文件作用域。对于没有作用域的宏变量,宏函数强烈建议不使用。
作用域的一些缺点:
- 虽然可以通过作用域来区分两个命名相同的类型,但是还是具有迷惑性。
- 内联命名空间会让命名空间内部的成员摆脱限制,让人迷惑。
通过多重嵌套来定义namespace,会让完整的命名空间比较冗长。所以,我们使用命名空间的建议如下:
对于变量,常量和类型定义尽可能使用namespace,减少全局作用域的冲突
- 不要在头文件中使用using namespace
- 不要使用内联命名空间
- 鼓励在.cpp文件中通过匿名namespace或者static来封装,防止不必要的定义通过API暴露出去。
优先使用C++特性而不是C特性
C++比起C语言更加类型安全,更加抽象。我们更推荐使用C++的语言特性来编程,比如使用string而不是char*
, 使用vector而不是原生数组,使用namespace而不是static。