Golang错误处理

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
}

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}

// errorString is a trivial implementation of error.
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 main

import (
"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 main

import (
"errors"
"fmt"
)

// ErrNotFound 定义 Sentinel Error
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 main

import (
"fmt"
)

// MyError 自定义错误类型
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 main

import (
"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 main

import (
"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 main

import (
"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 main

import (
"database/sql"
"errors"
"fmt"
)

// 模拟 DAO 层查询
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 main

import (
"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 {
// %+v 会打印堆栈信息
fmt.Printf("Error: %+v\n", err)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}

Warp 会追踪栈的调用链。


Golang错误处理
https://mfzzf.github.io/2025/03/18/golang-error/
作者
Mzzf
发布于
2025年3月18日
许可协议