.error包装
f, err := os.Open("config.yml")Open函数返回两个值:文件描述符和 error。在成功时,错误为nil,否则它有一些值。
if err != nil { // ... do something return err }通常的错误处理模式是检查错误是否为空。你可能会说,这样的明确性伴随着冗长的语言。是的,Golang代码中经常散布着if err != nil。哦,拜托,我是不是每次都要检查err !=nil?
const input = "The quick brown fox jumps over lazy dog." scanner := bufio.NewScanner(strings.NewReader(input)) scanner.Split(bufio.ScanWords) count := 0 // We're not checking the error, just iterate for scanner.Scan() { count++ } // See if there was any error in the end if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "reading input:", err) }scanner.Scan()在有匹配的情况下返回true,在没有匹配或有错误的情况下返回false。任何在扫描过程中可能发生的错误都会被记录下来。在扫描循环结束后,将推迟检查:不需要每次迭代都检查。
type error interface { Error() string }开箱即用,Go提供了内置的Error.New构造函数。
errors.New("missing ID parameter")或更灵活的fmt.Errorf():
return fmt.Errorf("Unsupported message type: %q", msgType)返回error
//堆代码 duidaima.com func Divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("divide by zero prohibited") } return a/b, nil }
type HttpError struct { StatusCode int Err error } func (h HttpError) Error() string { return fmt.Sprintf("HTTP error: %v (%d)", h.Err, h.StatusCode) }error 是值,所以要像其他结构一样创建和返回:
func handlerFunc(r http.Request) error { // ... // something went wrong: return HttpError{ StatusCode: 404, Err: errors.New("product not found"), } // ... }然后使用类型断言将一般的 error 转换为我们特定的HttpError:
he, ok := err.(HttpError) if ok { log.Printf("HTTP error with status = %d", he.StatusCode) }或者使用推荐的、更安全的方式:
var he HttpError if errors.As(err, &he) { log.Printf("HTTP error with status = %d", he.StatusCode) }日志
func readFromFile() (string, error) { data, err := os.ReadFile("wrong file name") if err != nil { return "", errors.Wrap(err, "readFromFile") } return string(data), nil } func readConfig() (string, error) { data, err := readFromFile() if err != nil { return "", errors.Wrap(err, "readConfig") } // ... return data, nil } func main() { conf, err := readConfig() if err != nil { log.Printf("Cannot read: %v", err) } } output: 2022/03/16 00:37:54 Cannot read: readConfig: readFromFile: open wrong file name: no such file or directory在每个包裹着错误的层次,你可以添加一条信息。一个原因,也可能只是一个函数名称或其他抽象层次的标识。
errors.Cause(err)来检索堆栈中的第一个错误。该函数是安全的,所以当提供一个没有包装的错误时,它将返回错误。
err = fmt.Errorf("read file: %w", err)注意%w格式指定符,它用错误的文本值来代替。请记住,这种形式不保留堆栈痕迹。
log.Printf("Cannot read: %+v", err) output: 2022/07/22 18:51:54 Cannot read: open wrong file name: no such file or directory readFromFile awesomeProject/learnWrapping.readFromFile /Code/learn/go/awesomeProject/learnWrapping/wrapping.go:13 awesomeProject/learnWrapping.readConfig /Code/learn/go/awesomeProject/learnWrapping/wrapping.go:19 awesomeProject/learnWrapping.Main /Code/learn/go/awesomeProject/learnWrapping/wrapping.go:28 main.main /Users/tomek/Code/learn/go/awesomeProject/main.go:6堆栈跟踪可以通过另一种有点隐晦的方式来检索。包含堆栈跟踪的错误实现了私有的stackTracer接口(它是私有的,因为名字以小写字母开头)。但是,这是Go,你可以在你的代码中重新声明这个接口:
type stackTracer interface { StackTrace() errors.StackTrace }并单独访问每个堆栈框架:
if sterr, ok := err.(stackTracer); ok { log.Printf("Stack trace:") for n, f := range sterr.StackTrace() { fmt.Printf("%d: %s %n:%d\n", n, f, f, f) } } output: 2022/07/23 15:11:17 Stack trace: 0: wrapping.go readConfig:21 1: wrapping.go Main:32 2: main.go main:6 3: proc.go main:250 4: asm_arm64.s goexit:1259在堆栈深处处理错误,在顶部记录
func readFromFile() (string, error) { data, err := os.ReadFile("wrong file name") if err != nil { log.Printf("readFromFile failed") return "", errors.Wrap(err, "readFromFile") } return string(data), nil } func readConfig() (string, error) { data, err := readFromFile() if err != nil { return "", errors.Wrap(err, "readConfig") } // ... return data, nil } func main() { conf, err := readConfig() if err != nil { log.Printf("Cannot read: %v", err) } }结果我们会在日志中看到重复的语句:
2022/09/13 14:22:41 Cannot read file: "dummyfile.txt" 2022/09/13 14:22:41 Cannot read: readConfig: readFromFile: open dummyfile.txt: no such file or directory在上面的例子中,包装好的错误组合提供了足够的上下文来确定错误的原因。在第一行,我们得到了断章取义的声明,这说明了什么。当然,你可以将Context下游传递给每一个方法,但我不认为这样做的好处会超过污染造成的不清晰。