重构被定义为这样一种过程:以不改变代码外部行为、同时改善其内部结构的方式,修改一个软件系统。
我们可以归纳出管理者对代码重构通常采取的三种做法:
- 迭代式重构:即使团队拥有最优秀的架构师、设计师和开发者,您的应用程序也不可能在第一个迭代中就做到完美。一种既不投入大量资金、也不浪费时间的务实重构方式,是把重构融入开发流程,在每个迭代之后进行。
- 必要时才重构:应用程序部署之后,会收到一些缺陷报告;如果修复它们很费时间,或者某些客户需求很难开发并集成到现有系统中,重构就可以成为提升代码库质量的好办法。但在这种情况下风险可能很大,必须小心避免在现有代码中引入回归。
- 从不重构:有时,即使现有应用程序存在许多问题,也从不进行重构,因为管理层不愿在这个过程上投入;支持团队不得不应付所有缺陷和反馈带来的压力。
如果您的团队定期重构 C++ 项目,并选择了使用一些新的 C++11/C++14 特性,那么看看其他知名 C++ 项目是如何重构代码以采用新标准的,会很有参考价值。
Chrome 浏览器就是一个很好的研究对象:他们定期重构代码库;新标准一经批准并获得编译器支持,Chrome 开发团队就选择向这些新标准迈进。
Chrome 浏览器是一个成熟的 C++ 项目,探究它的代码是如何实现和设计的非常有意思。事实上,Chrome 拥有数百万用户,这要求其开发团队产出高效的代码。
如果您决定重构自己的 C++ 代码库以采用新标准的特性,值得看一看这份有趣的Google 文档,它列出了允许使用的 C++11/C++14 特性,也列出了被禁用的特性。
这份文档中最有价值的部分是被禁用特性的清单及其禁用原因。仔细研究这份清单、看看它是否也适用于您的项目,是非常值得的。
下面是文档中被禁用的 C++11/C++14 特性:
C++11 被禁用的特性
| 特性或库 | 代码片段 | 说明 | 文档链接 | 备注与讨论帖 |
|---|---|---|---|---|
| 内联命名空间(Inline Namespaces) | inline namespace foo { ... } |
让命名空间的版本管理更方便 | 内联命名空间 | 在Google 风格指南中被禁用。尚不清楚它将如何与组件协同工作。 |
long long类型 |
long long var = value; |
至少 64 位的整数 | 基本类型 | 如果需要 64 位数字,请使用 stdint.h 类型。讨论帖 |
| 引用限定的成员函数(Ref-qualified Member Functions) | class T {
void f() & {}
void f() && {}
};
t.f(); // first
T().f(); // second
std::move(t).f(); // second |
允许类成员函数仅绑定到作为右值或左值的 |this|。 | const、volatile 和引用限定的成员函数 | 在Google 风格指南中被禁用。只有在获得styleguide/c++/OWNERS。讨论帖 |
| 用户定义字面量(User-Defined Literals) | 类型 var = literal_value_类型 |
允许用户自定义字面量表达式 | 用户定义字面量 | 在Google 风格指南。 |
| thread_local 存储类 | thread_local int foo = 1; |
将变量放入线程局部存储。 | 存储期 | 在 Mac 上有一些出人意料的影响(讨论、fork)。请使用SequenceLocalStorageSlot以获得序列(sequence)支持,其他情况使用ThreadLocal/ThreadLocalStorage。 |
C++14 被禁用的特性
| 特性 | 代码片段 | 说明 | 文档链接 | 备注与讨论帖 |
|---|---|---|---|---|
| 函数返回类型推导 | auto f() { return 42; }
decltype(auto) g() { return 42; } |
允许根据模板或decltype规则,从函数的 return 语句自动推导其返回类型。 |
返回类型推导 | 暂时禁用,因为它可能导致 clang 陷入无限循环。我们预计在该缺陷修复后会允许使用。它的使用场景应该很少,主要用于抽象模板代码。讨论帖 |
| 泛型 lambda | [](const auto& x) { ... } |
允许使用auto(按照适用于模板的规则)推导 lambda 参数类型。 |
lambda 表达式 | 暂时禁用,因为它可能导致 clang 陷入无限循环。我们预计在该缺陷修复后会允许使用。讨论帖 |
C++11 被禁用的库特性
| 特性 | 代码片段 | 说明 | 文档链接 | 备注与讨论帖 |
|---|---|---|---|---|
| 对齐存储(Aligned storage) | std::aligned_storage<10, 128> |
为需要特定对齐的对象提供未初始化的存储。 | std::aligned_storage | MSVC 2017 的实现在大于 sizeof(double)= 8 字节的边界上不做对齐。请改用alignas(128) char foo[10];。发现此问题的补丁。 |
| 绑定操作(Bind Operations) | std::bind(function, args, ...) |
声明一个绑定了特定参数的函数对象 | std::bind | 请改用base::Bind。与std::bind、base::Bind通过禁止绑定捕获型 lambda、并强制调用者把裸指针声明为Unretained。讨论帖 |
| C 浮点环境 | <cfenv>、<fenv.h> |
为兼容 C 的代码提供浮点状态标志和控制模式 | 标准库头文件 <cfenv> | 因担心它会助长更重度依赖模板的接口风格,被Google 风格指南禁用。 |
| 日期和时间工具 | <chrono> |
标准日期和时间库 | 日期和时间工具 | 与Time中的base/中的许多类重叠。暂时继续使用base/类。 |
| 异常 | <exception> |
对异常抛出和处理的增强 | 标准库头文件 <exception> | 异常被Google 风格指南禁用,并在 Chromium 编译中被关闭。请注意,前面已明确允许使用noexcept说明符。讨论帖 |
| 函数对象(Function Objects) | std::function |
封装一个标准的多态函数 | std::function | 请改用base::Callback。与std::function、base::Callback直接支持 Chromium 的引用计数类和弱指针,并处理额外的线程安全问题。讨论帖 |
| 比率模板类(Ratio Template Class) | std::ratio<分子, 分母> |
提供编译期有理数 | std::ratio | 因担心它会助长更重度依赖模板的接口风格,被Google 风格指南禁用。 |
| 正则表达式 | <regex> |
标准正则表达式库 | 正则表达式库 | 与 Chromium 中的许多正则表达式库重叠。拿不准时,请使用 re2。 |
| 共享指针(Shared Pointers) | std::shared_ptr |
通过引用计数实现指针的共享所有权 | std::shared_ptr | 对 Chromium 而言还需要更多评估,目前对采用这一特性的推动力也不足。Google 风格指南。讨论帖 |
| 线程库(Thread Library) | <thread>及相关头文件,包括<future>、<mutex>、<condition_variable> |
提供使用std::thread及其相关设施的标准多线程库 |
线程支持库 | 与base/中的许多类重叠。暂时继续使用base/类。base::Thread与MessageLoop紧密耦合,这将使其难以被替换。我们应该研究使用标准的互斥量(mutex)或 unique_lock 等,来替换我们的锁/同步类。 |
C++14 被禁用的库特性
本节列出 Chromium 代码库中不允许使用的 C++14 库特性。
| 特性 | 代码片段 | 说明 | 文档链接 | 备注与讨论帖 |
|---|---|---|---|---|
std::chrono字面量 |
using namespace std::chrono_literals;
auto timeout = 30s; |
让std::chrono类型更容易构造。 |
std::literals::chrono_literals::operator""s | 因<chrono>被禁用而禁用。 |
结语
研究知名开源项目的源代码、理解它们的设计与实现选择,也许是学习如何编写高效代码的最佳方式之一。知名的 C++ 项目通常由努力践行最佳实践的 C++ 专家开发。
总而言之,看一看 Chromium 的源代码吧——它绝对值得您投入时间。
