(原文链接:https://abseil.io/tips/76 译者:clangpp@gmail.com)
每周贴士 #76: 使用absl::Status
- 最初发布于:2014-05-04
- 作者:Titus Winters
- 更新于:2020-02-06
- 短链接:abseil.io/tips/76
有同学提问在什么时候、该怎么使用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
不能被忽略,为什么Status
还可以有成员函数IgnoreError()
?设想你是代码审查员,正在审查CallFoo1()
或CallFoo2()
。代码审查员看到后者后会思考“这个函数可能返回错误,但是代码作者认为可以忽略它。对不对?”CallFoo1()
不会引发这样的思考。
允许调用者在上下文更丰富的地方处理错误
在你的代码不清楚该如何处理错误的时候,返回Status
,让拥有更多上下文的调用者来处理这个错误。
例如,在本地打印日志可能会影响效率,比如在写基础框架代码的时候。如果你的代码在紧凑循环中被调用,就算调用std::cout
也许都会太贵。其他情况下,用户也许根本不在乎某一次调用是否成功,但会被垃圾日志烦死。
很多情况下打印日志是合理的,但是返回Status
的函数不需要通过LOG
来解释为什么执行失败:它们可以返回错误代码和错误信息,让调用者决定如何正确地处理错误。
这不就是重新发明异常吗?
Google编程规范(出了名地)禁用了异常(关于这一点的讨论比其他任何禁用特性都要多)。人们很容易认为absl::Status
是“土法炼钢”(poor-man)的异常机制,且附带很多额外开销。但除了表面看起来像,absl::Status
区别于异常在于,它必须被显式处理,而不是作为未处理异常一路退栈到底。它强制工程师决定如何处理错误,然后显式地以可编译代码的形式说明之。最后,以absl::Status
返回错误,要比抛出(throw)和接受(catch)异常的执行速度快几个数量级。这些特性在写代码的时候有点烦人,但每个读代码的人都是纯赚,Google整体也因此得益。
结论
错误处理是做容易搞砸的事情之一:它们天然是边缘情况。如Status
这样的工具增加了跨API边界、跨项目、跨进程以及跨语言的错误处理一致性,帮助我们最小化了诸如“我们的错误处理有问题”这样的坑。如果你在设计一个需要表达失败可能性的接口,且没有特别强烈的原因选择其他方案,请考虑使用Status
。