一、良好的命名
源代码是更多是给开发人员阅读的而不是给编译器编译的,因此,源代码应该具有良好的可读性。为了让其他人能尽量读懂你写的代码,良好的命名是其中的关键因素。
具体为:源代码文件、命名空间、类、模板、函数、参数、变量名、常量名等都应该具有有意义且富有表现力的名字。
如果给变量、类或函数想出合适的名称很困难,这很可能表明代码存在某些困难或设计缺陷,这时候应该找到并解决命名困难的根本原因。
以下是对良好命名的具体建议。
1.1、名称应该自注释
即不需要注释解释名称的含义,能够看到名称就能够理解其用途。
不好的命名例子:
uint unm;
bool flag;
QList<Costomers> list;
QString data;
好的命名例子:
uint numberOfPerson;
bool isChanged;
QList<Costomers> costomersList;
QString personalInformation;
应注意不要使用太冗长的名称。如果变量的上下文很清晰,则可以使用较短和较少描述性的名称。例如:
struct personInfo
{
QString nameOfPerson;
int ageOfPerson;
};
知道这是关于人的信息的结构体,变量中使用 person 就多余了,可写作:
struct personInfo
{
QString name;
int age;
};
(只是一个说明的例子,一般人也不这么写代码的...)
1.2、域驱动设计
即以应用程序域的概念和元素命名组件、类、函数等。
例如:
class QueryStudentCourseSelection
{
public:
Student identityStudent(const int StudentID);
CourseList getAllSelectedCourseList(const int StudentID);
Teacher viewTeacherInformation(std::string teacherName);
};
这是一个学生选课相关的类,它里面的各种定义使用了和教育领域相关的元素,这使得这个类的功能相当容易理解。
1.3、分层的结构
对于分层的结构,层级越深入名称就越具体。
class CarFactory
{
public:
CarFactory();
void produceCars(int number);
};
class GreenCarFactory : public CarFactory
{
public:
GreenCarFactory();
void produceGreenCars(int number);
};
1.4、避免冗余名称
应该避免使用这种名称:
class Movie
{
private:
QString stringTitle;
};
电影名称是一个字符串,名称前面的 string 明显多余。
1.5、避免使用晦涩难懂的缩写
写变量名称时应该使用完整的单词而不是单词缩写,这会增加代码的可读性。
QString PWD;//不建议写法
QString password;//建议写法
1.6、避免使用匈牙利命名法
现在的开发工具相当智能,不必要通过变量名来知道其类型。
1.7、避免相同的名称用于不同的目的
可能会是阅读代码的人产生误解。
二、注释
2.1、基本准则
代码应该尽量自解释,尽量让代码本身好懂。即尽量做到不写注释,除非是要特别说明的地方。
2.2、不要写块注释
块注释:在函数前面或者类前面的说明其功能或标识版权信息的占多行的注释文字。这种块注释正面意义不大。
2.3、应该写注释的情况
当一段代码具有高度的内在复杂性,以至于没有深入的专业知识的人无法轻易理解。
由于某些原因故意偏离了良好的设计原则时。比如故意复制了一段代码,这时候可以在注释里解释为什么这样做。
2.4、注释的原则
确保注释可以为阅读代码的人员提供重要的信息,这些信息通常在代码里面体现得不明显。
应该解释代码为什么这样做,而不是怎样去做。即应该解释为什么这段代码要存在,而要了解代码怎样去做应该看的是代码本身而不是注释。
注释应该尽可能短且富有表现力。
注释也需要维护。
三、函数
3.1、一个函数只做一件事
当函数代码有如下迹象说明应该考虑拆分函数:
函数的代码行数非常多。
当为函数起一个有表现力的名字以描述函数的功能时,函数名称中无法避免地使用连词“和”、“或”。
当发现不自觉地将函数体分成好几部分时。
函数包含了大量的 if-else、switch-case语句。
函数的传入参数较多,特别是 bool 类型的参数。
3.2、让函数尽可能小
这里可能有很多人不同意但我赞同作者的看法:函数应该尽可能小,理想情况是四五行,最多十几行。
关于函数调用的开销是不成问题的,现代的c++编译器及其擅长优化且好的CPU每秒可执行上百亿条指令。真正导致性能出现问题的往往是糟糕的架构和设计,只有在极端特殊的情况下才需要担心函数调用的开销。
3.3、函数的参数和返回值
函数的参数应该尽可能地少。最好是没有参数或有1个参数,尽一切可能使参数不超过3个。
除了必要情况外应该尽可能使成员函数不含参数。成员函数一般用于操作类的内部状态,如果成员函数没有用到成员变量那需要审视该函数是否放到类外作为一个独立函数更好。
避免使用标志型的参数。标志型的参数一般是bool类型的,函数根据传入的标志执行不同的操作。这违反了一个函数做一件事的原则,这时候应该对函数进行拆分。
避免为了使函数返回多个值而使用输出型参数(非常指针、非常引用)。想要函数返回多个值时:如果返回值之间关联不大可以使用 std::tuple 或 std::pair;而如果返回的值之间内聚力很高那么可以将这些数据封装成类或结构体处理。
当函数返回指针类型的变量时应避免返回 nullptr。因为返回 nullptr 后将不得不在调用函数后的代码增加一条返回的指针为空时的处理路径或增加判空操作的代码。处理办法:一是函数在栈上创建对象并返回,接收时使用 std::move 语义,这样避免了昂贵的拷贝构造;二是调用函数前在栈上创建一个对象然后把对象的非常引用作为参数传递给函数。
3.4、用好 const 限定符
违反 const 会导致编译错误,这可以节省调试时间,且使用 const 可以使编译器支持一些优化,即提高程序执行性能。
正确解读 const 修饰的对象:const 总是修饰它左边的内容,左边没有内容时修饰它右边的内容(指向对象的指针还是对象本身)。
四、强制类型转换
尽可能避免使用强制类型转换,应该尝试消除导致使用类型转换的设计问题。
无法避免使用时应仅使用c++风格的类型转换,因为编译器会检查c++风格的类型转换而不会检查C语言风格的类型转换。
尽量使用 static_cast<>、const_cast<>、如非极端必要切不可使用 reinterpret_cast<>、dynamic_cast<>
五、宏
不要使用宏,如果要定义一个常量应该使用 const。