Go 语言没有像 try/catch 这样的异常捕获机制,它的错误处理核心是:将错误作为返回值显式返回,由调用方自行决定如何处理。这种设计让错误处理更清晰、更可控,也符合 Go 语言 "简洁、显式" 的设计哲学。
一、基础:error 接口
Go 语言的错误处理基石是内置的 error 接口,它的定义非常简单:
Go
type error interface {
Error() string
}
任何实现了 Error() string 方法的类型,都可以作为错误类型返回。
1. 最基础的错误使用方式
Go
package main
import (
"errors"
"fmt"
)
// 定义一个可能出错的函数:除法运算,除数不能为0
func divide(a, b int) (int, error) {
if b == 0 {
// 使用errors.New创建基础错误
return 0, errors.New("除数不能为0")
}
// 无错误时,返回结果+nil(表示无错误)
return a / b, nil
}
func main() {
// 调用函数,接收结果和错误
res, err := divide(10, 0)
// 核心:先检查错误,再使用结果
if err != nil {
fmt.Printf("运算失败:%v\n", err) // 输出:运算失败:除数不能为0
} else {
fmt.Printf("运算结果:%d\n", res)
}
// 正常情况
res2, err2 := divide(10, 2)
if err2 != nil {
fmt.Printf("运算失败:%v\n", err2)
} else {
fmt.Printf("运算结果:%d\n", res2) // 输出:运算结果:5
}
}
2. 自定义错误类型
当需要携带更多错误信息(如错误码、上下文)时,可以自定义实现 error 接口的类型:
Go
package main
import (
"fmt"
)
// 自定义错误类型,包含错误码和错误信息
type MyError struct {
Code int // 错误码
Msg string // 错误信息
}
// 实现error接口的Error()方法
func (e *MyError) Error() string {
return fmt.Sprintf("错误码:%d,信息:%s", e.Code, e.Msg)
}
// 模拟业务函数返回自定义错误
func getUserInfo(id int) (string, error) {
if id <= 0 {
return "", &MyError{Code: 400, Msg: "用户ID不合法"}
}
return fmt.Sprintf("用户%d的信息", id), nil
}
func main() {
info, err := getUserInfo(-1)
if err != nil {
// 类型断言,获取自定义错误的详细信息
if myErr, ok := err.(*MyError); ok {
fmt.Printf("业务错误:码=%d,信息=%s\n", myErr.Code, myErr.Msg)
} else {
fmt.Printf("未知错误:%v\n", err)
}
return
}
fmt.Println(info)
}
3. fmt.Errorf:带格式化信息的错误
Go 1.13+ 新增了 %w 占位符,支持错误包装(Error Wrapping),可以保留原始错误信息:
Go
package main
import (
"errors"
"fmt"
)
func readFile() error {
// 模拟底层错误
baseErr := errors.New("文件不存在")
// 包装错误,添加上下文信息
return fmt.Errorf("读取文件失败:%w", baseErr)
}
func main() {
err := readFile()
if err != nil {
fmt.Println("错误信息:", err) // 输出:错误信息:读取文件失败:文件不存在
// 使用errors.Is检查原始错误
if errors.Is(err, errors.New("文件不存在")) {
fmt.Println("确认:底层错误是文件不存在")
}
// 模拟另一个自定义错误
customErr := &MyError{Code: 500, Msg: "服务器错误"}
wrappedErr := fmt.Errorf("处理失败:%w", customErr)
// 使用errors.As提取包装的自定义错误
var targetErr *MyError
if errors.As(wrappedErr, &targetErr) {
fmt.Printf("提取到自定义错误:码=%d,信息=%s\n", targetErr.Code, targetErr.Msg)
}
}
}
// 复用前面定义的MyError类型
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("错误码:%d,信息:%s", e.Code, e.Msg)
}
4. panic/recover:处理极端错误
Go 语言中 panic 用于表示不可恢复的严重错误 (如数组越界、空指针),recover 用于捕获 panic 并恢复程序(仅在 defer 函数中有效),这不是常规的错误处理方式,仅用于兜底:
Go
package main
import "fmt"
func riskyFunc() {
defer func() {
// 捕获panic,恢复程序
if r := recover(); r != nil {
fmt.Printf("捕获到panic:%v\n", r) // 输出:捕获到panic:运行时错误:除数为0
}
}()
// 触发panic
fmt.Println(10 / 0)
}
func main() {
riskyFunc()
fmt.Println("程序继续执行") // 能正常输出,说明程序被恢复
}
二、错误处理的最佳实践
- 优先返回错误,而非 panic :只有程序无法继续运行的极端情况才用
panic(如初始化失败)。 - 错误检查要及时:调用函数后,先检查错误,再使用返回值,这是 Go 的编码惯例。
- 错误信息要具体:包含上下文(如文件名、参数值),方便定位问题,避免模糊的 "操作失败"。
- 合理包装错误 :使用
fmt.Errorf("%w")保留原始错误,便于上层调用方排查根因。 - 避免重复处理错误 :不要在多层调用中重复打印错误,由最外层统一处理即可。
总结
- Go 语言错误处理的核心是将错误作为返回值显式返回 ,依赖内置的
error接口,无try/catch机制。 - 基础错误用
errors.New创建,带格式化信息用fmt.Errorf,Go 1.13+ 支持%w包装错误,通过errors.Is/As解析。 panic/recover仅用于处理极端错误,常规业务逻辑应优先使用返回错误的方式,且遵循 "先检查错误,再使用结果" 的惯例。