写在前面

这篇文章算是我在学习现代C++路上中捡到的各种零零散散的知识点的合集,总结在这里方便之后不断地回来翻阅。以项目构建的流程角度分为以下类

  • 程序测试

  • 程序设计

  • 代码实现

这些原则的来源包括但不限于:

汇总到一起作为本人开发是参考的小抄,方便查阅

PS: 有关C++语法的奇技淫巧和最佳实践,我都是统一参考Google Style Guide,就不在这里做copy paste了

程序测试

自动测试框架

我们的代码首先要具备的就是一个自动测试框架,它要包含基于实现逻辑的单元测试,以及基于用户行为的BDD回归测试。一个好的自动测试可以让我们放心的重构代码、扩展代码。从另一个方面来说,如何测试很难构建,那么这段代码的设计很可能并不是最优的

持续构建测试

我们需要一些持续构建的自动测试工具来帮助我们确保每一次提交的可靠性,不管是gitlab的ci,还是github的workflow,或者其他的持续构建工具。

编译器警告

我们需要编译器来告诉我们,我们的代码是否正确符合C++标准。注意-Wall并不是GCC和Clang提供的所有warning,-Wextra能提供更多的信息,但还是建议使用-Wpedantic获取gcc/clang提供的以ANSI/ISO C标准列出的警告

静态分析

静态分析是我们构建代码的第一道防线。现在有许多免费开源的静态分析工具,其中推荐CPPCHECK和clang-tidy

使用 Sanitizers

Sanitizers是一个运行时分析工具,推荐在开发时永远开启Address sanitiz和UB Sanitizer,在包含多线程时开启Thread sanitizer

模糊测试(Fuzzing)和变异测试(Mutating)

我们开发人员设计的测试用例往往是带有思维惯性的,对于有些极端或误用的情况测试不到,这是我们需要这些工具来弥补我们的思维惯性

Fuzzing

模糊测试工具能够生成各种长度的随机字符串,这种测试能够约束你用合适的方法去处理这些数据,模糊测试工具分析那些从你的测试执行过程中生成出来的覆盖率数据并且使用那些信息去删除多余的测试并且产生新且特殊的测试用例。

对于C++,我们可以使用American Fuzzy Lop(AFL)来进行模糊测试

Mutating

编译测试通过修改你的代码来完成测试。它修改你的代码逻辑,之后任何能通过的测试用例都意味着要么你的代码存在bug,要么测试用例有缺陷。

例子

1
2
3
4
5
6
7
8
bool greaterThanFive(const int value) { 
return value > 5; // comparison
}

void tests() {
assert(greaterThanFive(6));
assert(!greaterThanFive(4));
}

变异测试会修改你的代码如

1
2
3
bool greaterThanFive(const int value) {
return value < 5; // mutated
}

程序设计

KISS原则

这是一个已经被说烂了的元组原则,不做不必要的实现,是你的代码尽可能简洁、简单的完成。但我想说KISS不仅是用在程序逻辑的实现中,更应该用在设计层面上。一个好的设计能让你的代码少走20年弯路,使其以一个好的代码质量开头,这无疑对之后的维护和扩展是更有帮助的。

持续重构

当然我们并不是全知全能,我们在项目开始迭代之后也一定有层出不穷的奇妙想法,可能是在某天睡梦中你对某个feature有了更好的设计方案,这时不要害怕去重构代码,在保证有完备的测试体系下,不断地去重构代码,这能有效的阻止随着代码增长带来的技术债滚雪球似的堆积。

copy paste

当你在copy paste时,想想是否可以抽象为一个函数,或者一个模板?

代码实现

用const修饰一切常量

如果形参在函数内部不会有修改的行为,将形参也声明为const

善用constexpr

如果一个常量值在编译时已经可以得到,使用constexpr。不要让#define成为你的默认选择

多使用auto

auto在泛型编程时可以极大的简化我们的代码,不要在花费功夫去看这个返回值应该是什么类型了

range-for循环

对容器进行遍历时,使用range-for循环

1
2
3
for (const auto &element : container) {
...
}

但是不要在range-for循环时修改容器自身,如果需要修改,使用老办法吧

善用STL算法

一个场景:检查一个容器内是否有大于42的数

使用循环,我们需要

1
2
3
4
5
6
7
8
bool has_value;
for (const auto &element : container) {
if (element > 42)
{
has_value = true;
break;
}
}

使用循环

1
const auto has_value = std::any_of(begin(container), end(container), greater_than(12));

使用算法在某些场景下可能会使你的代码没有那么易读,根据场景使用它

使用模板

模板时DRY(Dont repeat yourself)原则的典型设计,不要害怕去使用它。

当然,使用模板时,给你的类型一个更有意义的名字,而不是用T

使用智能指针

尽量避免使用new,在使用到堆的情况下,使用std::make_unique<>()std::make_shared<>()来确保安全的资源管理

跳过C++11

如果你在正在转向”现代C++", 请跳过Cpp11版本, Cpp14修复了许多Cpp11的漏洞。

其中语言层面的特性包括:

  1. C11 版本的constexpr 隐式地指定了所有成员函数为const(即不能修改this), 这在C14已经被改变。

  2. C++11缺少对函数对auto 返回类型的推导(lambdas有)

  3. C++11没有auto或者可变Lambda参数的

  4. C++14新增[[deprecated]]特性

  5. C++14新增了数字分隔符, 比如1’000’000

  6. 在C++14里constexpr 函数可以有多个return

库层面特性包括:

  1. std::make_unique 在C++14中加入

  2. C++11没有std::exchange

  3. C++14新增了对std::array的constexpr支持

  4. cbegin, cend, crbegin, 和crend 这些自由函数(free function ,没有入参的函数) 为了和begin 和end这些在C++11加入的标准容器中的自由函数保持一致而被加入了。