每周技巧 #171:避免哨兵值
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #171: Avoid Sentinel Values。
原文最初作为 TotW #171 发布于 2019 年 11 月 8 日。
作者:Hyrum Wright
更新于 2020 年 4 月 6 日。
快捷链接:abseil.io/tips/171
哨兵值是在特定上下文中具有特殊含义的值。比如考虑下面这个 API:
|
|
除 -5 之外,int 的每个值都被文档说明为 AccountBalance 的有效返回值。直觉上这有点奇怪:调用者应该只检查 -5,还是任何负值都可以可靠地表示“账户已关闭”?如果系统之后支持负余额,这个 API 又该如何调整才能返回负值?
使用哨兵值会增加调用代码的复杂度。如果调用者很严谨,它会显式检查哨兵值:
|
|
一些调用者可能会检查比规范更宽的取值范围:
|
|
还有一些调用者可能会完全忽略哨兵值,假设它在实践中并不会出现:
|
|
哨兵值的问题
上面的例子展示了使用哨兵值时的一些常见问题。其他问题还包括:
- 不同系统可能使用不同的哨兵值,比如单个负值、所有负值、无穷值,或者任意某个值。传达特殊值含义的唯一方式是文档。
- 哨兵值仍然属于该类型的有效取值域,因此类型系统不会强制调用方或被调用方承认某个值可能无效。当代码和注释不一致时,通常两者都是错的。
- 哨兵值限制接口演进,因为某个具体哨兵值将来可能变成系统中的有效值。
- 一个系统的哨兵值可能是另一个系统的有效值。在多个系统交互时,这会增加认知负担和代码复杂度。
忘记检查约定的哨兵值是常见 bug。最好的情况下,未经检查的哨兵值会在运行时立刻让系统崩溃。更常见的是,它会继续在系统中传播,并一路产生错误结果。
改用 std::optional
不要用特殊值表示信息不可用或无效,而应使用 std::optional。
|
|
新版 AccountBalance() 的调用者现在必须显式查看返回值内部是否真的有余额,这个动作本身就表达了结果可能无效。除非有额外文档说明,调用者可以假设任何有效的 int 值都可能从该函数返回,而不需要排除某些具体哨兵值。这种简化能让调用代码的意图更清晰。
|
|
下次你想在系统中使用哨兵值时,请认真考虑改用合适的 std::optional。
相关阅读
- 关于使用
std::optional传递参数的更多信息,见 TotW #163。 - 如果你在判断何时使用
std::optional、何时使用std::unique_ptr,见 TotW #123。
本节目录