每周技巧 #45:避免标志,尤其是在库代码中
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #45: Avoid Flags, Especially in Library Code。
原文最初作为 TotW #45 发布于 2013 年 6 月 3 日。
“我真正想要的是:我的代码行为由一个全局变量控制,这个变量无法静态预测,使用情况记录不完整,而且只能非常困难地从我的代码中移除。”– 从来没人这么说过
在生产代码中常见地使用 flag,尤其是在库中使用 flag,是一个错误。除非真的有必要,否则不要使用 flag。好了,我们说出来了。
flag 是全局变量,而且更糟:你无法通过阅读代码知道这个变量的值。flag 不仅可能在启动时设置,还可能之后通过任意方式被修改。如果你的二进制中运行着一个服务器,通常无法保证 flag 的值在一次循环到下一次循环之间保持不变;如果它发生变化,也没有任何通知,更没有任何机制可以寻找这种变化。
如果你的生产环境会直接记录每个二进制的调用,并保存这些日志,那很好。大多数环境并不是这样。对库代码来说,这种不确定性尤其危险:你怎么判断某个特性的使用是否真的已经死亡?简单答案是:你不能。
flag 也会让彻底清除死代码变得困难。在迁移到新后端时,你也许会以为移除遗留代码只是删掉不必要的构建依赖,然后执行历史上最令人满足的 git rm。你错了。如果你的遗留二进制定义了数百个 flag,并且这些 flag 被生产代码引用,那么简单删除死代码会给发布工程师造成巨大问题:这样的改动之后,几乎没有作业能够启动。
这一切最糟糕的部分是什么?Google 在 2012 年初做过一项分析,在上面提到的数据保留限制内,我们能看到的大多数 C++ flag 实际上从未变化过。
不过,flag 在某些用例中是合适的:没有通过 flag 启用的回溯,调试就不是那个味道了。特性 flag 如果处理得当(并且之后被清理),完全有正当理由。真正需要由英勇 SRE 调整的旋钮,是很有用的安全网。更广泛地说,用于向二进制传递名称/值输入、并且只在 main() 中使用的 flag,比位置参数更易维护。
即便有这些注意事项,我们也该认真审视自己对 flag 的使用。下次你想在库里添加一个 flag 时,请花点时间寻找更好的方式。显式传递配置:这几乎总是更容易正确推理,也当然更容易维护。考虑把数值 flag 变成编译期常量。如果你在代码评审中遇到新的 flag,请提出质疑。每一次引入 flag 都应该有充分理由。