本周小贴士#94:调用点可读性和bool参数

Posted -飞鹤-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#94:调用点可读性和bool参数相关的知识,希望对你有一定的参考价值。

作为totw/94最初发表于2015年4月27日

由Geoff Romer (gromer@google.com)创作

修订于2017年10月25日

“在各种虚假文化中,过早与抽象概念进行对话最有可能对…智力的增长造成致命的影响。”——George Boole

假定你遇到如下代码:

int main(int argc, char* argv[]) {
  ParseCommandLineFlags(&argc, &argv, false);
}

你能讲出这段代码是做什么的,特别是最后一个参数是什么意思吗?假定你之前已经遇到这个函数,并且你知道最后一个参数是在调用之后处理命令行参数标志是否留在argv中。你能区分哪个是真,哪个是假吗?

你当然不知道,因为这是假设的,但即便在真实代码中,我们用脑子做事比记住每个函数参数的意义更有意义,用我们的时间去做更好的事情而不是为我们遇到的每个函数调用去查阅文档。仅通过观看调用点,我们应该能够很容易地猜出函数调用的意义。

好好选择函数名是让函数调用变得可读的关键,但是它们通常还不够。我们通常需要参数本身给我们一些它们意味着什么的线索。例如,你可能不知道absl::string_view s(x, y)做了什么;如果你之前从未见过string_view,但是absl::string_view s(my_str.data(), my_str.size());和absl::sring_view s(“foo”);就清楚多了。bool参数的问题是,在调用点的参数通常是字面的true和false,并且正如我们在ParseCommandLineFlags()示例中看到的,那不会给读者提供参数意义的上下文提示。如果这里有多个bool参数,那么这个问题就会变得更加复杂,因为现在你有一个新的问题,那就是弄明白哪个参数是哪个。

因此,你会如何修复像那个示例的代码呢?一个(糟糕的)可能性是像这样做:

int main(int argc, char* argv[]) {
  ParseCommandLineFlags(&argc, &argv, false /* preserve flags */);
}

这种方法的缺点很明显:不清楚该注释是描述参数的意义还是参数的影响。换句话说,它是说我们正在保留这个标志,还是说我们正在保留的参数是false?即便注释设法说清楚了,注释仍然存在与代码脱离同步的风险。

更好的方法是在注释中指明参数的名字:

int main(int argc, char* argv[]) {
  ParseCommandLineFlags(&argc, &argv, /*remove_flags=*/false);
}

这更清楚了,并且更不可能发生与代码不同步的事。Clang-tidy甚至将检查注释是否有正确的参数名。更少的歧义,但是更长的变体是用作解释变量。

int main(int argc, char* argv[]) {
  const bool remove_flags = false;
  ParseCommandLineFlags(&argc, &argv, remove_flags);
}

但是,编译器不会检查解释变量的名称,因此它们可能是错误的。当你有多个bool参数时,这是一个特别的问题,这些参数可能被调用者调整顺序。

所有这些方法还是依赖程序员始终记得添加注释或变量,并且正确地执行(尽管clang-tide会检查名称注释的正确性)。

在许多情况下,最佳的解决方案是首选避免使用bool参数,而使用枚举。例如,ParseCommandLineFlag()可以声明如下:

enum ShouldRemoveFlags { kDontRemoveFlags, kRemoveFlags };

void ParseCommandLineFlags(int* argc, char*** argv, ShouldRemoveFlags remove_flags);

因此调用可以是这样的:

int main(int argc, char* argv[]) {
  ParseCommandLineFlags(&argc, &argv, kDontRemoveFlags);
}

你也使用enum class,类似于TotW 86中的描述,但是在这种情况下,你可能想使用一个稍微不同的命名转换,如:

enum class ShouldRemoveFlags { kNo, kYes };int main(int argc, char* argv[]) {
  ParseCommandLineFlags(&argc, &argv, ShouldRemoveFlags::kNo);
}

显然,这种方法必须在函数定义时实现;你不能在调用点选择加入它(你可以伪造它,但没什么好处)。因此当你定义一个函数时,特别是如果它会被广泛使用,你有责任仔细考虑调用点看起来如何,尤其是要对bool参数持怀疑态度。

以上是关于本周小贴士#94:调用点可读性和bool参数的主要内容,如果未能解决你的问题,请参考以下文章

本周小贴士#141:注意隐式转换到bool

本周小贴士#141:注意隐式转换到bool

本周小贴士#146:默认vs值初始化

本周小贴士#146:默认vs值初始化

本周小贴士#74:委托和继承构造函数

本周小贴士#116: 保留对参数的引用