本周小贴士#171: 避免标志值

作为TotW#171最初发表于2019年11月8日

由Hyrum Wright创作

标志值是在特定上下文中具有特殊意义的值。例如,考虑以下API:

//返回帐户余额,如果帐户已关闭,则返回-5。
int AccountBalance();

除了-5外,int类型的每一个值都被记录为AccountBalance的有效返回值。直觉上,这感觉有点奇怪:调用方只应该检查-5特别吗?任何负数值是否可靠地表示“账户关闭”?当系统支持负余额并且需要调整API以返回负值时会发生什么?

使用标志值会增加调用代码的复杂性。如果调用方严格执行,则明确检查标志值:

int balance = AccountBalance();
if (balance == -5) {
std::cerr << "account closed";
return;
}
// 在此处使用balance

某些调用方可能会检查比规定范围更广泛的值:

int balance = AccountBalance();
if (balance <= 0) {
std::cerr << "where is my account?";
return;
}
// 在此处使用balance

有些调用方可能会完全忽略标志值,假设它实际上不会出现:

int balance = AccountBalance();
// 在此处使用balance

标志值的问题

上面的例子说明了使用标志值的一些常见问题。其他问题包括:

  • 不同的系统可能使用不同的标志值,如单个负值,所有负值,无限值或任意值。唯一传达特殊值的方法是通过文档。
  • 标志值仍然是类型有效值域的一部分,因此调用方和被调用方都不受类型系统强制要求,以承认该值可能无效。当代码和注释不一致时,通常都会出现问题。
  • 标志值限制接口演进,因为特定的标志可能会某天成为该系统中可用的有效值。
  • 一个系统的标志值是另一个系统的有效值,在与多个系统进行交互时增加了认知负荷和代码复杂度。
    忘记检查指定的标志值是一种常见错误。在最好的情况下,使用未经检查的标志值将立即在运行时崩溃系统。更频繁地,未经检查的标志值可能会继续通过系统传播,产生错误结果。

改用std::optional

使用std::optional来表示不可用或无效信息,而不是使用特殊值。

// 返回帐户余额,如果帐户已关闭,则返回std::nullopt。
std::optional<int> AccountBalance();

我们的新版本AccountBalance()的调用者现在必须明确查看返回值内部是否存在潜在余额,从而在此过程中信号表示结果可能无效。除非有其他文档说明,否则调用方可以假设此函数可以返回任何有效的int值,而不排除特定的标志值。这种简化可以澄清调用代码的意图。

std::optional<int> balance = AccountBalance();

if (!balance.has_value()) {
std::cerr << "Account doesn't exist";
return;
}
// 在此处使用*balance

下次您想要在系统中使用标志值时,请强烈考虑改为使用适当的std::optional。

另请参阅

  • 有关使用std::optional作为参数传递值的更多信息,请参见TotW#163。
  • 有关在何时使用std::optional而不是std::unique_ptr的帮助,请参见TotW#123。

你可能感兴趣的:(C++,Tips,of,the,Week,c++,开发语言)