C++ 代码整洁之道:C++17 可持续软件开发模式实践 (22):构建安全体系 2.5.10

阅读数:9 2019 年 12 月 4 日 18:48

C++代码整洁之道:C++17可持续软件开发模式实践(22):构建安全体系 2.5.10

(不要混淆测试代码和产品代码)

内容简介
本书致力于讲述 C++ 整洁代码之道!如果你想让自己写的代码更加整洁,那么这本书适合你阅读。本书需要熟悉 C++ 语言的基本概念,才能有效的掌握其中的内容。如果你只是想从 C++ 开发开始,并且没有 C++ 语言的基础知识,你应该首先选择一个好的 C++ 入门的练习项目。此外,本书也不包含任何深奥的技巧和杂乱的知识点。我知道 C++ 有很多令人兴奋的技巧,但这些通常不是整洁代码的精神,也不是现代 C++ 的代码风格。除此之外,这本书为了帮助 C++ 程序员提高技能水平,并举例说明如何编写易于理解的、灵活的、可维护的和高效的 C++ 代码。即使你是一个经验丰富的 C++ 开发人员,这本书中也有一些值得学习的地方,我认为这些值得学习的地方能够促进你的工作。书中所提出的原则和实践可以应用于新的软件系统,有时被称为“绿地项目”,以及具有悠久历史的遗留系统,通常被称为“棕地项目”。

有时开发人员产生了一个想法,用测试代码来装备他们的生产代码。例如,在测试期间,一个类可能以如下方式包含了处理协作类的依赖关系的代码:

代码 2-6 在测试过程中处理依赖关系的一种可能的解决方案

复制代码
#include <memory>
#include "DataAccessObject.h"
#include "CustomerDAO.h"
#include "FakeDAOForTest.h"
using DataAccessObjectPtr = std::unique_ptr<DataAccessObject>;
class Customer {
public:
Customer() {}
explicit Customer(bool testMode) : inTestMode(testMode) {}
void save() {
DataAccessObjectPtr dataAccessObject = getDataAccessObject();
// ...use dataAccessObject to save this customer...
};
// ...
private:
DataAccessObjectPtr getDataAccessObject() const {
if (inTestMode) {
return std::make_unique<FakeDAOForTest>();
} else {
return std::make_unique<CustomerDAO>();
}
}
// ...more operations here...
bool inTestMode{ false };
// ...more attributes here...
};

DataAccessObject 是特定 DAO(数据访问对象)的抽象基类,在本例中为 CustomerDAO 和 FakeDAOForTest,后者就是所谓的测试替身(fake object),这是一个用于测试的虚拟对象(参见本章后面的 2.5.12 节),目的是替换真正的 DAO,因为我们不想测试它,并且我们不想在测试期间保存 Customer 的数据(谨记我关于数据库的建议)。使用两个 DAO 中的哪一个由布尔数据成员 inTestMode 控制。

这段代码虽然可行,但这一解决方案有几个缺点。

首先,我们的生产代码会混杂测试代码,虽然初看并不显眼,但它会增加产品复杂度并降低代码的可读性。我们需要一个额外的成员来区分系统的测试模式和生产使用,这个布尔成员与客户无关,更不用说系统的域了。而且不难想象系统中的许多类都需要这种类型的成员。

此外,Customer 类依赖于 CustomerDAO 和 FakeDAOForTest,你可以在源代码头部的包含文件列表中看到它,这意味着在生产环境中测试虚拟类 FakeDAOForTest 也是系统的一部分,我们寄希望于测试替身的代码永远不会在生产中被调用,但是它确实被编译、链接并部署在了生产中。

当然,也有一些更优雅的方法来处理这些依赖关系,并保证生产代码不受测试代码的影响。例如,我们可以在 Customer::save() 中注入特定的 DAO 作为一个参考参数。

代码 2-7 避免依赖测试代码(1)

复制代码
class DataAccessObject;
class Customer {
public:
void save(DataAccessObject& dataAccessObject) {
// ...use dataAccessObject to save this customer...
}
// ...
};

或者,也可以在构造 Customer 类型的实例期间完成。在这种情况下,我们必须将 DAO 的一个引用作为类的成员属性。此外,我们必须通过编译器禁止自动生成默认构造函数,因为我们不希望 Customer 的任何用户可以创建一个未正确初始化的实例。

代码 2-8 避免依赖测试代码(2)

复制代码
class DataAccessObject;
class Customer {
public:
Customer() = delete; //C++11 的语法,指示编译器不生成 Customer() 构造函数
Customer(DataAccessObject& dataAccessObject) : dataAccessObject(dataAccessObject) {}
void save() {
// ...use member dataAccessObject to save this customer...
}
// ...
private:
DataAccessObject& dataAccessObject;
// ..
};

deleted 函数 [C++11]
在 C++ 中,如果有些类型成员没有被定义,编译器会自动为这些类型生成所谓的特殊成员函数(默认构造函数、拷贝构造函数、拷贝赋值运算符和析构函数)。从 C++11 开始,这个特殊成员函数列表多了移动构造函数和移动赋值运算符。C++11(及更高版本)提供了一种简单且声明性的方法来阻止自动创建任何特殊成员函数、普通成员函数和非成员函数,你可以删除它们。例如,你可以通过以下方式阻止创建默认构造函数:

复制代码
class Clazz {
public:
Clazz() = delete;
};

另一个例子:你可以删除 new 运算符以防止在堆上动态分配一个类:

复制代码
class Clazz {
public:
void* operator new(std::size_t) = delete;
};

第三种替代方案是特定的 DAO 可以由 Customer 类已知的一个工厂(请参阅第 9 章中有关设计模式的 Factory 模式部分)来创建。如果系统在测试环境中运行,我们可以从外部配置 Factory 以创建所需的 DAO。无论你选择哪种可能的解决方案,Customer 类都能与测试代码脱离,Customer 与特定的 DAO 没有依赖关系。

C++代码整洁之道:C++17可持续软件开发模式实践(22):构建安全体系 2.5.10

购书地址 https://item.jd.com/12599914.html?dist=jd

评论

发布