每周技巧 #161:好的局部变量和坏的局部变量
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #161: Good Locals and Bad Locals。
原文最初作为 TotW #161 发布于 2019 年 4 月 16 日。
更新于 2020 年 4 月 6 日。
快捷链接:abseil.io/tips/161
我们也许会在全局范围内惊慌失措,但承受痛苦是在局部。—— Jonathan Franzen
概要
局部变量很好,但也可能被过度使用。我们常常可以通过把局部变量限制在能提供特定收益的场景中,来简化代码。
建议
只有当下面一项或多项成立时,才使用局部变量:
- 它们的名字增加了有用文档。
- 它们简化了过度复杂的表达式。
- 它们把重复表达式抽取出来,让人类(以及较小程度上让编译器)清楚知道每次都是同一个值。
- 对象生命周期需要跨越多个语句(例如,对该对象的引用会保留到单个语句结束之后,或者变量保存一个会在其生命周期内更新的值)。
其他情况下,可以考虑移除一层间接性:消除局部变量,并在使用处直接写表达式。
理由
给值命名会给代码理解增加一层间接性,除非变量名完全捕捉了其含义中相关的方面。在 C++ 中给值一个名字,也会把它暴露给作用域的其余部分。它还会影响“值类别”,因为每个具名变量都是左值,即使它声明为右值引用并由右值初始化。这可能需要额外使用 std::move,而代码评审时需要小心这些用法,避免 use-after-move bug。考虑到这些缺点,局部变量最好保留给能提供特定收益的场景。
示例:局部变量的糟糕用法
消除立即返回的局部变量
作为消除无帮助局部变量的简单例子,与其写:
|
|
不如写:
|
|
把被测表达式内联进 GoogleTest 的 EXPECT_THAT
|
|
这里变量名 actual 没有增加任何有用信息(EXPECT_THAT 总是把实际值作为第一个参数),它没有简化复杂表达式,而且它的值只使用一次。像下面这样内联表达式:
|
|
可以一眼看出正在测试什么;并且通过不给 actual 命名,确保它不会被无意复用。它还允许测试框架在错误输出中显示失败调用。
注意:较短版本隐藏了 SortedAges 的期望类型。如果验证类型很重要,可以考虑声明变量以显示其类型。
使用 matcher 消除测试中的变量
Matcher 可以让 EXPECT_THAT 直接表达我们对某个值的全部期望,从而帮助避免在测试中命名局部变量。不要写这样的代码:
|
|
这里我们必须小心写 ASSERT* 而不是 EXPECT* 以避免崩溃。我们可以直接在代码中表达意图:
|
|
示例:局部变量的良好用法
抽取重复表达式
|
|
这里的重复让代码冗长(有时还需要不幸的换行),也可能让读者需要更多努力才能看出这正在设置同一个 proto 消息的三个字段。使用局部变量为相关消息创建别名可以清理它:
|
|
在某些情况下,这也有助于编译器生成更好代码,因为它不需要证明重复表达式每次返回同一个值。不过要警惕过早优化:如果消除公共子表达式不能帮助人类读者,请先性能分析,再尝试帮助编译器。
给 pair 和 tuple 元素有意义的名字
虽然通常用带有有意义字段名的 struct 比用 pair 或 tuple 更好,但我们可以通过把有意义的别名绑定到它们的元素上,来缓解 pair 和 tuple 的问题。例如,与其写:
|
|
在 C++11 中可以写:
|
|
而在 C++17 中,我们可以更简单地使用“结构化绑定”达到同样的命名效果:
|
|