初面网初面网

错误处理

Go 的错误处理采用显式返回错误的方式,而不是传统的 try-catch 异常机制。函数通常把 error 作为最后一个返回值,调用方显式检查并处理。

主要机制:

机制说明
error 接口标准的错误表示
显式返回值通过函数返回值返回错误
自定义错误通过标准库或自定义类型创建错误
panic 和 recover处理不可恢复的严重错误

error 接口

error 是标准库定义的一个接口类型,任何实现了 Error() 方法的类型都可以作为错误:

type error interface {
	Error() string
}

errors.New 创建错误

使用 errors 包创建简单错误,函数通常在最后返回错误信息:

package main

import (
	"errors"
	"fmt"
)

func Sqrt(f float64) (float64, error) {
	if f < 0 {
		return 0, errors.New("math: square root of negative number")
	}
	return f * f, nil
}

func main() {
	result, err := Sqrt(-1)
	if err != nil {
		fmt.Println(err) // math: square root of negative number
	} else {
		fmt.Println(result)
	}
}

显式返回错误

调用函数时必须检查 error 是否为 nil:

package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

func main() {
	result, err := divide(10, 0)
	if err != nil {
		fmt.Println("Error:", err) // Error: division by zero
	} else {
		fmt.Println("Result:", result)
	}
}

fmt.Errorf 格式化错误

fmt.Errorf 可以带格式化参数生成错误信息,%w 可以包装错误,配合 errors.Is 和 errors.As 处理错误链:

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
	// %w 把 ErrNotFound 包装进新的错误
	return fmt.Errorf("database error: %w", ErrNotFound)
}

func main() {
	err := findItem(1)
	if errors.Is(err, ErrNotFound) {
		fmt.Println("Item not found")
	} else {
		fmt.Println("Other error:", err)
	}
}

自定义错误类型

通过定义实现了 Error() 方法的类型来扩展错误,可以携带更多信息:

package main

import (
	"errors"
	"fmt"
)

type DivideError struct {
	Dividend int
	Divisor  int
}

func (e *DivideError) Error() string {
	return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, &DivideError{Dividend: a, Divisor: b}
	}
	return a / b, nil
}

// errors.As 把错误转换为特定类型以便进一步处理
func main() {
	_, err := divide(10, 0)
	var de *DivideError
	if errors.As(err, &de) {
		fmt.Printf("自定义错误 - 被除数: %d, 除数: %d\n", de.Dividend, de.Divisor)
	}
}

panic 与 recover

panic 用于处理不可恢复的错误,会导致程序崩溃并输出堆栈信息;recover 用于捕获 panic,避免程序崩溃。

recover 只能写在 defer 注册的函数内,详见 20-defer.md

package main

import "fmt"

func safeFunction() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered from panic:", r)
		}
	}()
	panic("something went wrong")
}

func main() {
	fmt.Println("Starting program...")
	safeFunction()
	fmt.Println("Program continued after panic")
}

输出结果:

Starting program...
Recovered from panic: something went wrong
Program continued after panic

panic 后不再执行后面的代码,会按逆序执行 defer 再逐层向外扩散,直到被 recover 捕获或程序结束

错误处理最佳实践

调用第三方函数时必须显式处理错误:

package main

import (
	"fmt"
	"os"
)

func readConfig(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		// 返回包装后的错误,方便定位调用链
		return fmt.Errorf("读取配置失败: %w", err)
	}
	fmt.Println("配置内容:", string(data))
	return nil
}

func main() {
	if err := readConfig("nonexist.txt"); err != nil {
		fmt.Println("处理错误:", err)
	}
}

最佳实践:函数优先返回 error;不要吞掉错误;使用 %w 包装错误保留调用链;能用 error 处理的场景不要轻易用 panic

更新于 2026/8/13