首先,您习惯的 try-catch 样式的扩展版本,显然是从 jimt 的答案和 PeterSO 的答案中借用的。
package main
import "fmt"
// Some dummy library functions with different signatures.
// Per idiomatic Go, they return error values if they have a problem.
func f1(in string) (out int, err error) {
return len(in), err
}
func f2(in int) (out int, err error) {
return in + 1, err
}
func f3(in int) (out float64, err error) {
return float64(in) + .5, err
}
func main() {
inval := "one"
// calc3 three is the function you want to call that does a computation
// involving f1, f2, and f3 and returns any error that crops up.
outval, err := calc3(inval)
fmt.Println("inval: ", inval)
fmt.Println("outval:", outval)
fmt.Println("err: ", err)
}
func calc3(in string) (out float64, err error) {
// Ignore the following big comment and the deferred function for a moment,
// skip to the comment on the return statement, the last line of calc3...
defer func() {
// After viewing what the fXp function do, this function can make
// sense. As a deferred function it runs whenever calc3 returns--
// whether a panic has happened or not.
//
// It first calls recover. If no panic has happened, recover returns
// nil and calc3 is allowed to return normally.
//
// Otherwise it does a type assertion (the value.(type) syntax)
// to make sure that x is of type error and to get the actual error
// value.
//
// It does a tricky thing then. The deferred function, being a
// function literal, is a closure. Specifically, it has access to
// calc3's return value "err" and can force calc3 to return an error.
// A line simply saying "err = xErr" would be enough, but we can
// do better by annotating the error (something specific from f1,
// f2, or f3) with the context in which it occurred (calc3).
// It allows calc3 to return then, with this descriptive error.
//
// If x is somehow non-nil and yet not an error value that we are
// expecting, we re-panic with this value, effectively passing it on
// to allow a higer level function to catch it.
if x := recover(); x != nil {
if xErr, ok := x.(error); ok {
err = fmt.Errorf("calc3 error: %v", xErr)
return
}
panic(x)
}
}()
// ... this is the way you want to write your code, without "breaking
// the flow."
return f3p(f2p(f1p(in))), nil
}
// So, notice that we wrote the computation in calc3 not with the original
// fX functions, but with fXp functions. These are wrappers that catch
// any error and panic, removing the error from the function signature.
// Yes, you must write a wrapper for each library function you want to call.
// It's pretty easy though:
func f1p(in string) int {
v, err := f1(in)
if err != nil {
panic(err)
}
return v
}
func f2p(in int) int {
v, err := f2(in)
if err != nil {
panic(err)
}
return v
}
func f3p(in int) float64 {
v, err := f3(in)
if err != nil {
panic(err)
}
return v
}
// Now that you've seen the wrappers that panic rather than returning errors,
// go back and look at the big comment in the deferred function in calc3.
因此,您可能会抗议您要求更轻松,而事实并非如此。整体上没有参数,但是如果库函数都返回错误值并且你想在没有错误值的情况下链接函数调用,可用的解决方案是包装库函数,并且包装器非常薄且易于编写。唯一困难的部分是延迟函数,但它是一种您可以学习和重用的模式,而且它只有几行代码。
我不想过多地宣传这个解决方案,因为它不是经常使用的。这是一个有效的模式,并且确实有一些合适的用例。
正如 jimt 所提到的,错误处理是一个大主题。“在 Go 中进行错误处理的好方法是什么?” 对于 SO 来说,这将是一个很好的问题,除了它不符合“整本书”标准的问题。我可以想象一本关于 Go 错误处理主题的整本书。
相反,我将提供我的一般观察,如果你只是开始使用错误值而不是试图让它们消失,一段时间后你就会开始理解这样做的好处。当你第一次在现实世界的程序中编写它时,看起来像我们在这里使用的玩具示例中的 if 语句的详细阶梯可能仍然看起来像 if 语句的详细阶梯。但是,当您实际上需要处理这些错误时,您会回到代码并突然将其视为存根,所有这些都在等待您用真正的错误处理代码充实。您可以看到要做什么,因为导致错误的代码就在那里。您可以防止用户看到晦涩的低级错误消息,而是显示一些有意义的内容。作为程序员,你会被提示做正确的事情,而不是接受默认的事情。
要获得更全面的答案,一个很好的资源是文章错误处理和 Go。如果您搜索Go-Nuts 消息,那里也会对此事进行长时间的讨论。标准库中的函数相互调用相当多,(惊喜)因此标准库的源代码包含许多处理错误的示例。这些都是很好的例子,因为代码是由 Go 作者编写的,他们正在推广这种使用错误值的编程风格。