在我看来,谷歌的例外替代方案是
- GO:多值返回“return val, err;”
- GO,C++:无检查(提前返回)
- GO,C++:“处理该死的错误”(我的术语)
C++:断言(表达式)
GO:defer/panic/recover 是在提出此问题后添加的语言功能
多值回报是否足以作为替代品?为什么“断言”被认为是替代品?如果发生错误处理不正确,Google 是否认为程序停止是可以的?
Go 的一个不同寻常的特性是函数和方法可以返回多个值。这可以用来改进 C 程序中的几个笨拙的习惯用法:带内错误返回(例如 EOF 的 -1)和修改参数。
在 C 语言中,写入错误由负计数表示,错误代码隐藏在易失性位置。在 Go 中,Write 可以返回一个计数和一个错误:“是的,你写了一些字节,但不是全部,因为你填满了设备”。os 包中 *File.Write 的签名为:
func (file *File) Write(b []byte) (n int, err Error)
正如文档所说,它返回写入的字节数和当 n != len(b) 时的非零错误。这是一种常见的风格;有关更多示例,请参见错误处理部分。
Go 函数的返回或结果“参数”可以指定名称并用作常规变量,就像传入参数一样。当命名时,它们在函数开始时被初始化为它们的类型的零值;如果函数执行没有参数的 return 语句,则使用结果参数的当前值作为返回值。
这些名称不是强制性的,但它们可以使代码更短更清晰:它们是文档。如果我们命名 nextInt 的结果,很明显哪个返回的 int 是哪个。
func nextInt(b []byte, pos int) (value, nextPos int) {
因为命名结果被初始化并与一个简单的返回相关联,所以它们可以简化和澄清。这是一个很好地使用它们的 io.ReadFull 版本:
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
for len(buf) > 0 && err == nil {
var nr int;
nr, err = r.Read(buf);
n += nr;
buf = buf[nr:len(buf)];
}
return;
}
例外是一个类似的故事。已经提出了许多异常设计,但每一种都大大增加了语言和运行时的复杂性。就其本质而言,异常跨越函数,甚至可能跨越 goroutine。它们具有广泛的影响。人们还担心它们会对图书馆产生影响。根据定义,它们是卓越的,但使用其他支持它们的语言的经验表明它们对库和接口规范具有深远的影响。如果能找到一种设计,让它们真正出类拔萃,而又不会鼓励常见错误变成需要每个程序员进行补偿的特殊控制流,那就太好了。
像泛型一样,异常仍然是一个悬而未决的问题。
决定:
从表面上看,使用例外的好处大于成本,尤其是在新项目中。但是,对于现有代码,异常的引入对所有依赖代码都有影响。如果异常可以传播到新项目之外,那么将新项目集成到现有的无异常代码中也会出现问题。由于 Google 现有的大多数 C++ 代码都没有准备好处理异常,因此采用会产生异常的新代码相对困难。
鉴于 Google 现有的代码不能容忍异常,使用异常的成本比新项目的成本要高一些。转换过程会很慢并且容易出错。我们不认为异常的可用替代方法(例如错误代码和断言)会带来很大的负担。
我们反对使用例外的建议不是基于哲学或道德依据,而是基于实际的依据。因为我们想在 Google 使用我们的开源项目,如果这些项目使用异常就很难做到,所以我们也需要建议不要在 Google 开源项目中使用异常。如果我们不得不从头开始重新做一遍,事情可能会有所不同。
Defer 语句允许我们考虑在打开每个文件后立即关闭它,从而保证无论函数中返回语句的数量如何,文件都将被关闭。
defer 语句的行为是直接且可预测的。有三个简单的规则:
1. 当 defer 语句被评估时,延迟函数的参数被评估。
在这个例子中,当 Println 调用被延迟时,表达式“i”被计算。函数返回后,延迟调用将打印“0”。
func a() { i := 0 defer fmt.Println(i) i++ return }
2. 延迟函数调用在周围函数返回后按后进先出的顺序执行。此函数打印“3210”:
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } }
3. 延迟函数可以读取并分配给返回函数的命名返回值。
在此示例中,延迟函数在周围函数返回后递增返回值 i。因此,此函数返回 2:
func c() (i int) { defer func() { i++ }() return 1 }
这样方便修改函数的错误返回值;我们很快就会看到一个例子。
Panic 是一个内置函数,它可以停止普通的控制流程并开始恐慌。当函数 F 调用 panic 时,F 的执行停止,F 中的所有延迟函数都正常执行,然后 F 返回其调用者。对调用者来说,F 的行为就像是对恐慌的调用。该进程继续向上堆栈,直到当前 goroutine 中的所有函数都返回,此时程序崩溃。恐慌可以通过直接调用恐慌来启动。它们也可能是由运行时错误引起的,例如越界数组访问。
Recover 是一个内置函数,可以重新控制恐慌的 goroutine。恢复仅在延迟函数中有用。在正常执行期间,recover 调用将返回 nil 并且没有其他效果。如果当前的 goroutine 正在恐慌,对 recovery 的调用将捕获为 panic 提供的值并恢复正常执行。
这是一个演示恐慌和延迟机制的示例程序:
<snip>
有关恐慌和恢复的真实示例,请参阅 Go 标准库中的 json 包。它使用一组递归函数对 JSON 编码的数据进行解码。当遇到格式错误的 JSON 时,解析器调用 panic 是将堆栈展开到顶层函数调用,该函数调用会从 panic 中恢复并返回适当的错误值(参见 decode.go 中的 'error' 和 'unmarshal' 函数) . 在 regexp 包的 Compile 例程中有一个类似的技术示例。Go 库中的约定是,即使包在内部使用了 panic,它的外部 API 仍然会显示显式的错误返回值。
defer 的其他用途(除了前面给出的 file.Close() 示例之外)包括释放互斥锁:
mu.Lock() defer mu.Unlock