Google C++每周贴士 #161: 局部变量有好有坏

(原文链接:https://abseil.io/tips/161 译者:clangpp@gmail.com)

每周贴士 #161: 局部变量有好有坏

We may freak out globally, but we suffer locally. – Jonathan Franzen

概要

局部变量挺好的,但是容易被滥用。通过将其限制在它能提供确切好处的地方,我们通常可以让代码更简化。

建议

只有如下一条或多条适用的时候,才使用局部变量:

  • 该变量名增加了有用的文档。
  • 该变量简化了过于复杂的表达式。
  • 该变量提炼出了重复的表达式,让人(以及优化能力稍弱的编译器)一眼就能看出它的值每次都一样。
  • 一个对象的生存期需要跨越多个语句(例如,因为对象的引用被保留到单个语句之后,或者因为变量保存了一个在生存期内被更新的值)。

在其他情况下,请考虑去除该局部变量,把表达式直接写在调用点,以减少一层间接性。

考量

为值命名给理解代码增加了一层间接性,除非变量名完全捕捉住了其含义的相关方面。在C++中,给一个值命名,也同时将其暴露给了当前作用域的剩余部分。它还会影响“值类别(value category)”,因为每个命名变量都是一个左值,即使它被声明为右值引用且以右值初始化。这样会需要额外的std::move来保证代码审查中的关注,以避免“移动后使用(use-after-move)”的错误。基于这些缺点,局部变量的使用最好还是保留在那些它能提供确切好处的地方。

示例:局部变量坏的用法

去除立即返回的局部变量

一个去除无用的局部变量的简单例子是,相比于

MyType value = SomeExpression(args);
return value;

更好的是

return SomeExpression(args);
把待测试的表达式内联进GoogleTest的EXPECT_THAT
auto actual = SortedAges(args);
EXPECT_THAT(actual, ElementsAre(21, 42, 63));

这里变量名actual没有增加任何有用的信息(EXPECT_THAT的第一个参数总是实际值),它并没有简化一个复杂的表达式,并且它的值只被使用了一次。将表达式内联如下

EXPECT_THAT(SortedAges(args), ElementsAre(21, 42, 63));

让人一眼就能看出被测试的东西,而且不为actual命名确保了它不会被意外地重用。这样也给测试框架提供了在报错信息中打印原始表达式的机会。

用匹配器(Matchers)来消除测试代码中的变量

匹配器允许EXPECT_THAT直接表达我们对一个值的所有期望,因此可以用来避免测试代码中对局部变量命名的需要。比起把代码写成这样

absl::optional<std::vector<int>> maybe_ages = GetAges(args);
ASSERT_NE(maybe_ages, absl::nullopt);
std::vector<int> ages = maybe_ages.value();
ASSERT_EQ(ages.size(), 3);
EXPECT_EQ(ages[0], 21);
EXPECT_EQ(ages[1], 42);
EXPECT_EQ(ages[2], 63);

(在其中我们不得不小心地写ASSERT*而不是EXPECT*,以避免程序崩溃),我们可以在代码中直接表达意图:

EXPECT_THAT(GetAges(args),
            Optional(ElementsAre(21, 42, 63)));

示例:局部变量好的用法

提炼出重复的表达式
myproto.mutable_submessage()->mutable_subsubmessage()->set_foo(21);
myproto.mutable_submessage()->mutable_subsubmessage()->set_bar(42);
myproto.mutable_submessage()->mutable_subsubmessage()->set_baz(63);

此处的重复使得代码冗长(有时候还需要讨厌的换行),而且需要读者更费劲地看出,这段代码在设置同一个proto的三个字段。用一个局部变量来给相关的message取个小名,可以让意图更清楚:

auto& subsubmessage = *myproto.mutable_submessage()->mutable_subsubmessage();
subsubmessage.set_foo(21);
subsubmessage.set_bar(42);
subsubmessage.set_baz(63);

注:如果可以提高可读性,那就请显式地指明引用的类型。只有在“避免繁杂的、显而易见的、不重要的类型名——类型并不能帮助读者更清晰地理解代码”的时候才使用auto

有时候这也能帮助编译器生成更好的代码,因为编译器不用去证明这段重复的表达式每次都返回相同的值。不过还是要小心过早优化:如果去除一个公共的子表达式不能帮助人类读者,那请在试图帮助编译器之前跑对比实验(profile)(译者注:以证明其真的有效果)。

为Pair和Tuple的元素取有意义的名字

虽然一般情况下使用含有有意义成员名的struct要比pairtuple更好,但是我们可以通过给pairtuple的成员绑定有意义的别名来缓解这个问题。例如,相比于

for (const auto& name_and_age : ages_by_name) {
  if (IsDisallowedName(name_and_age.first)) continue;
  if (name_and_age.second < 18) children.insert(name_and_age.first);
}

在C++11中我们可以写成

for (const auto& name_and_age : ages_by_name) {
  const auto& name = name_and_age.first;
  const auto& age = name_and_age.second;

  if (IsDisallowedName(name)) continue;
  if (age < 18) children.insert(name);
}

而在C++17中,我们可以简单地使用“结构化绑定”来达到取有意义名字的效果:

for (const auto& [name, age] : ages_by_name) {
  if (IsDisallowedName(name)) continue;
  if (age < 18) children.insert(name);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值