作为TotW#76最初发表于2014年5月4日
由Titus Winters创作
更新于2020年2月6日
有些人对什么时候以及如何使用absl::Status有疑问,因此这里有一些关于你为什么应该使用Status的理由,以及在使用时应该记住的事项。
交流意图及强制调用者处理错误
使用Status强制调用者处理错误的可能性。自2013年6月以来,不能简单地忽略返回Status对象的函数。也就是说,这段代码会产生一个编译错误:
absl::Status Foo();
void CallFoo1() {
Foo();
}
然而这些调用却很好:
void CallFoo2() {
Foo().IgnoreError();
}
void CallFoo3() {
if (!status.ok()) std::abort();
}
void CallFoo4() {
absl::Status status = Foo();
if (!status.ok()) std::cerr << status;
}
为什么Status有一个IgnoreError()方法就是可行的,然而我们通过所有努力来让编译器检查Status没有被忽略?假设你是一个代码审查员,正在查看CallFool1()或CallFoo2()。这后面的代码片段让审查员认为“这个函数可能有一个错误,但是作者认为可以忽略它,是吗?”CallFoo1()不会触发这样的响应。
允许调用者处理有更多上下文的错误
当你在代码中不能清晰地处理错误时,请使用Status。相反,返回一个Status,并且让有更合适观察力的调用者处理错误。
例如:本地记录日志可能影响性能,例如当编写一些基础架构代码时。如果你的代码是一个密集的循环中被调用,那么即便是设备std::cout的代价也是昂贵的。在其他情况,用户可能并不真地关心调用是否成功,并用发现日志垃圾邮件具有侵入性。
在许多情况下,打印日志是适合的,但是返回Status的函数不需要打印日志来解释为什么失败:它们能够返回失败码和错误字符串,让调用者决定正确地错误处理响应应该是什么。
这不就是重新发明异常吗?
谷歌风格指南以禁止异常而闻名(它的讨论比任何其他的禁止都要多)。将absl::Status视为困难户的异常机制可能很吸引人,并且有更多的开销。虽然表面上有相似之处,但是absl:Status的不同之处在需要显式处理,而不是以未处理的异常在不可见的情况下向上传递栈。它强制工程师决定如何处理错误,并且在可编译地代码中明确地记录。最后,使用absl::Status返回错误比抛出并捕获异常要快几个数量级。在写代码时,这些功能可能很繁重,但是对于每个必须阅读代码的人以及对于整个谷歌来说,这个结果是一个净胜利。
结论
错误处理是最容易出错的事情之一:这些本质上是一些边缘情况。借Status这样的程序增加跨API边界、项目、流程和语言的错误处理的一致性,以帮助我们最小化一大类“错误处理的问题”的故障。如果你设计了一个需要表达失败的接口,在你没非常充分的理由不这样做时,请使用Status。