Golang 的错误处理(Error Handling)
1. Errors 包 Error 是一个interface
,只要实现了Error() string
接口的结构体都是一个 Error。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type error interface { Error() string }func New (text string ) error { return &errorString{text} }type errorString struct { s string }func (e *errorString) Error() string { return e.s }
最简单的 error 示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "errors" "fmt" )func divide (a, b int ) (int , error ) { if b == 0 { return 0 , errors.New("division by zero is not allowed" ) } return a / b, nil }func main () { result, err := divide(4 , 0 ) if err != nil { fmt.Printf("Error: %v\n" , err) return } fmt.Printf("Result: %d\n" , result) }
2. Sentinel Error(预定义的 Error) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "errors" "fmt" )var ErrNotFound = errors.New("item not found" )func findItem (items []string , target string ) (string , error ) { for _, item := range items { if item == target { return item, nil } } return "" , ErrNotFound }func main () { items := []string {"apple" , "banana" , "orange" } _, err := findItem(items, "grape" ) if errors.Is(err, ErrNotFound) { fmt.Println("Error: Item not found in the list" ) } else if err != nil { fmt.Println("Unexpected error:" , err) } else { fmt.Println("Item found" ) } }
3. 自定义错误类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( "fmt" )type MyError struct { Operation string Err error }func (e *MyError) Error() string { return fmt.Sprintf("operation %s failed: %v" , e.Operation, e.Err) }func (e *MyError) Unwrap() error { return e.Err }func doSomething () error { return &MyError{ Operation: "file_read" , Err: fmt.Errorf("file not found" ), } }func main () { err := doSomething() if err != nil { fmt.Printf("Error: %v\n" , err) } }
4. Wrap Error 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "errors" "fmt" "os" )func readFile () error { return os.ErrNotExist }func processFile () error { err := readFile() if err != nil { return fmt.Errorf("processFile failed: %w" , err) } return nil }func main () { err := processFile() if err != nil { if errors.Is(err, os.ErrNotExist) { fmt.Println("File does not exist" ) } else { fmt.Printf("Unexpected error: %v\n" , err) } } }
Is 的源代码如下:
is
中会去unwrap
最后确定这两个 err
的最底层是否一致。ß
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func Is (err, target error ) bool { if err == nil || target == nil { return err == target } isComparable := reflectlite.TypeOf(target).Comparable() return is(err, target, isComparable) }func is (err, target error , targetComparable bool ) bool { for { if targetComparable && err == target { return true } if x, ok := err.(interface { Is(error ) bool }); ok && x.Is(target) { return true } switch x := err.(type ) { case interface { Unwrap() error }: err = x.Unwrap() if err == nil { return false } case interface { Unwrap() []error }: for _, err := range x.Unwrap() { if is(err, target, targetComparable) { return true } } return false default : return false } } }
5. As 提取指定错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "errors" "fmt" "os" )func readFile () error { return &os.PathError{ Op: "open" , Path: "/nonexistent/file" , Err: os.ErrNotExist, } }func main () { err := readFile() if err != nil { var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Printf("File operation failed: %v (path: %s)\n" , pathErr.Err, pathErr.Path) } else { fmt.Printf("Unexpected error: %v\n" , err) } } }
6. 整合日志记录与错误处理 在 Goroutine 的顶层统一记录日志并打印堆栈信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "errors" "fmt" )func process () error { return errors.New("something went wrong" ) }func main () { err := process() if err != nil { fmt.Printf("Error occurred: %+v\n" , err) return } fmt.Println("Process completed successfully" ) }
7. 模拟 DAO 错误处理 数据库操作中的错误处理示例,注重上下文信息的传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport ( "database/sql" "errors" "fmt" )func queryUserByID (id int ) (string , error ) { if id == 0 { return "" , sql.ErrNoRows } return "John Doe" , nil }func findUserByID (id int ) (string , error ) { user, err := queryUserByID(id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return "" , fmt.Errorf("user with ID %d not found: %w" , id, err) } return "" , fmt.Errorf("unexpected database error: %w" , err) } return user, nil }func main () { _, err := findUserByID(0 ) if err != nil { fmt.Println("Error:" , err) } }
8. pkg/errors 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" "github.com/pkg/errors" )func readFile () error { return errors.New("file not found" ) }func processFile () error { err := readFile() if err != nil { return errors.Wrap(err, "failed to process file" ) } return nil }func main () { err := processFile() if err != nil { fmt.Printf("Error: %+v\n" , err) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func Wrap (err error , message string ) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: message, } return &withStack{ err, callers(), } }
Warp 会追踪栈的调用链。