C++升级之C++整洁之道(二)——基本规范

1.良好命名

源代码是给开发人员阅读而不是给编译器编译,源代码应该具有良好的可读性,良好的命名是提高其可读性的关键因素。良好的类名、方法名和变量名将帮助你回忆你当初的意思。

源代码文件、命名空间、类、模版、函数、参数、变量和常量等,都应该具有有意义且富有表现力的名字。

命名应该注意以下问题:

(1)名称应该自解释:使用简单但是具有自我解释和自我描述的命名。在试图让代码保持整洁时,太冗长的命名方式是不合适的。如果使用变量的上下文很清晰,则可以使用较短和较少描述性的名称。

(2)使用域中的名称应该遵循领域驱动设计,将业务领域的事物和概念映射到代码中,使你的软件成为一个真实的系统模型。使用域中的术语,可以促进开发人员和其他利益相关人之间的沟通和交流,这将使代码更容易被任何参与解决问题的人理解。

(3)为了控制软件系统的复杂性,这些系统通常是分层的。软件系统的分层意味着将整个问题分解为较小的部分作为子任务,直到开发人员确信他们能够处理这些较小的部分。进行这种分解有领域驱动设计(DDD)和面向对象的分析和设计(OOAD),其中创建组件和类的基本标准是业务域。

通过这种分解,可以在不同的抽象级别创建软件模块:从大型组件或子系统开始,再到像类这样的非常小的构建模块。更高抽象级别的构建模块要完成的任务,应该通过与下一层较低抽象级别的模块的交互来完成。该方法引入的抽象级别也会对命名产生影响,每当我们在层次结构中深入一层时,元素的名称就会变得更加具体。如果我们更加深入地去研究分解层次结构,组件、类以及函数或方法的名称将变得越来越具体、详尽,因此也越来越长。

(4)避免冗余的名称:将类名作为成员变量名的一部分,那么给这个类提供一个能清晰表达上下文信息的命名是多余的。

(5)避免晦涩难懂的缩写:为变量或常量取名称的时候,请使用完整单词而不是晦涩难懂的缩写。其会显著降低代码的可读性。

(6)避免匈牙利命名和命名前缀:变量的类型很多时候被用作该变量的命名前缀,匈牙利命名在像C语言这种弱类型语言(面向过程编程)中有用,但是在面向对象和面向服务编程的时候,不要使用任何把类型信息加入变量名称。

(7)避免相同的名称用于不同的目的:一旦为任何软件实体、函数或变量定义了有意义且富有表现力的名称,就应该注意永远不要把这个名称用于任何其他的目的。

2.注释

(1)代码应讲述一个故事并且能自解释,必须尽可能避免注释。

(2)不要为易懂的代码写注释,代码本身在很大程度上是可以自行解释的。

(3)不要通过注释禁用代码,注释掉代码增加了代码的混乱度却没有带来实际意义上的好处,注释多处后代码将很快变得特别混乱,会增加很多阻碍可读性,注释掉的代码不具备质量保证,它们不会被编译器编译,也不会被测试和维护,除了快速进行测试外,不要通过注释禁用代码,同时还要有一个版本控制系统。如果不在使用某段代码,将其删除即可,在开发过程中进行快速测试时,暂时注释掉代码部分当然很有帮助,但必须确保不会把这类修改后的代码记录到版本控制系统中。

(4)不要写块注释,不要用注释代替版本控制

(5)如果一段代码具有高度的内在复杂性,以致每个没有深入专业知识的人无法轻易理解,那么注释就是合理的。比如:使用了复杂的数学算法或公式,或者处理非日常领域(自然现象的复杂模拟或复杂的加密方法)的软件系统。

需要编写必要注释的林规格原因是你可能故意偏离了良好的设计远策。

确保你的注释为代码增加了价值;应该解释为什么这样,而不是怎么做;尽量做到注释尽可能短而富有表现力。

3.函数

函数是任何软件系统的核心,它们代表代码行之上的第一个组织单位。编写良好的函数可以显著提高程序的可读性和可维护性。

(1)一个函数,必须有一个定义非常清晰的任务或功能,它应该用它的函数签名(函数型构)来表示。

当函数体量比较大;当你试图为这个函数找到一个有意义和有表现力的名字以描述该函数的功能时,函数名字中无法避免地连词;圈复杂度(分支线路)高;函数的入参比较多;函数体用空行垂直分隔成代表后续步骤的几个片段,这些片段的开头使用注释说明这些代码片段的功能;则函数做的事情太多。

(2)函数尽量小,小函数都能够降低程序执行的速度,任何函数都存在调用开销,C++编译器已经非常擅长优化了

(3)变量和常量的命名规则也大都适用于函数和方法,函数名称应该清晰、富有表现力,并且能自解释。使用容易理解的名称。

(4)理想的函数应该没有参数,数学意义上的函数总是至少有一个参数,这意味者“没有参数的函数”通常一定具有某种副作用。函数的参数应该尽可能的少,一个参数是比较理想的,类的成员函数一般没有参数。通常这些函数被用于操作对象的内部状态,或者被用于从对象中查询某些内容。

避免使用标志参数,其大多数为布尔类型,有时为枚举。

避免使用输出参数,不要传递或返回0。

一些避免使用指针的策略:首先在栈上构造对象而不是在堆上;可以将大型的对象直接作为返回值返回,而不用担心昂贵的拷贝构造代价,因为其为MOVE语义。

在函数的参数列表中,用const引用代替指针,利用C++的引用,其主要优点是不需要检查引用是否为空指针,另一个优点是不需要在引用操作符的帮主下解引用函数内部的任何内容,如果不可避免地处理指向资源的指针,请使用智能指针。

const正确性对于实现更好,更安全的C++代码来说是一种很实用的方法,使用const可以省去很多麻烦且节省调试时间,因为违法const会直接导致编译时错误,const的使用也可以支持编译器的一些优化算法,这意味着正确使用该限定符,也是一种提高程序执行性能的有效方法。

PS:尽可能使用const,并始终为变量或对象适当的声明以区分可变和不可变。

const最简单的用法是将变量定义为常量。

另一个用途是防止传递给函数的参数被改变:

unsigned int determineWeightofCar(Car const* car);//指针car指向Car类型的常量对象,即Car对象(“指针指向的对象”)不能被修改。
void lacquerCar(Car* const car);//指针car是一个Car类型的指针常量,即你可以修改Car对象,但不能修改指针(例如,为其指定Car的新实例)
unsigned int determinWeightofCar(Car const* const car);//指针和指针指向的内容都无法被修改
void printMessage(const std::string & message);//引用传递给函数,即不允许在函数内部更改被引用的字符
void printMessage(std::string const& message);//同上

PS:学会利用C++的函数代替传统C语言提高效率。

4.对于强制转换

double d {3.1415}
int i = (int) d

在这种情况下,double类型被降级为int类型。由于浮点数的小数被丢弃,因此这种显式转换伴随着精度的损失。使用C风格的显式转换就意味着:编写这行代码的程序员已经知道要产生的后果。当然,这肯定比隐式类型转换更好。不过,你应该用C++类型转换代替旧的C风格类型转换,就像这样:

int i = static_cast<int> (d)

C++风格的类型转换会在编译器编译期间进行检查,而C风格的强制转换却不会在编译期间进行检查,因此后者可能会在运行时出错,这可能会在运行时出错,这可能会导致严重缺陷或应用程序崩溃。

1.在任何情况下尽量避免类型转换(强制转换)。相反,尝试消除那些强制你使用转换的设计问题。

2.如果无法避免显式类型转换,则仅使用C++风格的类型转换,因为编译器会检查这些转换,永远不要使用旧的C风格的类型转换。

3.注意,也不要使用dynamic_cast<>,因为它被认为是一个糟糕的设计。对dynamic_cast<>的需求被当作一个可靠的标记,它表面当前的特殊化层次结构出现了问题。

4.在任何情况下,永远不要使用reinterpret_cast<>。这种类型转换被打上了不安全、不可移植和依赖于实现等标记。

5.避免使用宏

C语言最重要的遗产之一也许就是宏。宏是一段可以通过名称进行标识的代码。如果预处理器在编译时在源代码中找到了宏的名称,则该名称将其相关的代码片段替换。有一种宏是类似于对象的宏,通常用于为数字常量提供符合名称。

#define BUFFER_SIZE 1024
#define PI 3.14159265358979

#define MIN(a,b) (((a)<(b)?(a):(b))
#define MAX(a,b) (((a)>(b)?(a):(b))

宏是有潜在危险的,通常表现并不如预期那样,可能产生不必要的副作用。

替换类似对象的宏,可以用常量表达式来定义常量,可以用函数替代宏函数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值