Go语言基础:错误处理
1. 错误处理基础
Go语言通过`error`接口实现错误处理,标准库函数通常返回`(结果, error)`格式。当`error`为`nil`时表示操作成功,非`nil`时表示出错:
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result)
}
_, err = divide(5, 0)
if err != nil {
fmt.Println("错误:", err) // 输出"除数不能为0"
}
}
关键点说明:
- `error`接口定义:`type error interface { Error() string }`
- 优先使用`errors.New`创建简单错误
- 避免忽略错误(使用`_`占位符时需明确意图)
2. 自定义错误类型
通过实现`error`接口可以创建自定义错误类型,携带更多错误信息(如错误码、调用栈等):
package main
import (
"fmt"
)
// 自定义错误类型
type MyError struct {
Code int
Message string
}
// 实现error接口的Error方法
func (e *MyError) Error() string {
return fmt.Sprintf("错误码%d: %s", e.Code, e.Message)
}
func validateAge(age int) error {
if age < 0 {
return &MyError{Code: 400, Message: "年龄不能为负数"}
}
if age > 150 {
return &MyError{Code: 400, Message: "年龄超过合理范围"}
}
return nil
}
func main() {
err := validateAge(-5)
if err != nil {
fmt.Println(err) // 输出"错误码400: 年龄不能为负数"
}
}
设计优势:
- 可以携带结构化错误信息(错误码、上下文等)
- 通过类型断言区分不同错误类型
- 支持更精细的错误处理逻辑
3. defer与recover
`defer`语句用于延迟执行函数(通常用于资源清理),`recover`用于捕获`panic`(运行时错误)并恢复程序:
package main
import "fmt"
func safeDivide(a, b int) (int, error) {
defer func() {
// 捕获panic并转换为error
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
}
}()
if b == 0 {
panic("除数不能为0(panic)") // 触发panic
}
return a / b, nil
}
func main() {
// defer示例:确保文件关闭
file := openFile("data.txt")
defer file.Close() // 函数退出时关闭文件
fmt.Println("处理文件内容...")
// recover示例:捕获panic
result, err := safeDivide(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result)
}
}
// 模拟打开文件函数
func openFile(name string) *File {
fmt.Println("打开文件:", name)
return &File{name: name}
}
type File struct {
name string
}
func (f *File) Close() {
fmt.Println("关闭文件:", f.name)
}
使用原则:
- `defer`语句按后进先出顺序执行
- `recover`只能在`defer`函数中有效
- 避免滥用`panic`(仅用于不可恢复的错误)
4. 错误传播与包装
通过`fmt.Errorf`的`%w`动词可以包装错误,保留原始错误信息,便于上层调用链追踪:
package main
import (
"errors"
"fmt"
)
func readConfig() error {
_, err := parseConfig()
if err != nil {
// 包装原始错误
return fmt.Errorf("读取配置失败: %w", err)
}
return nil
}
func parseConfig() error {
return errors.New("配置文件格式错误")
}
func main() {
err := readConfig()
if err != nil {
fmt.Println("错误:", err)
// 解包原始错误
var originalErr error
if errors.As(err, &originalErr) {
fmt.Println("原始错误:", originalErr)
}
}
}
关键函数:
- `errors.Is(err, target)`:判断错误链中是否包含目标错误
- `errors.As(err, target)`:将错误链中的第一个匹配类型的错误赋值给target
- `fmt.Errorf("... %w ...", err)`:创建可包装的错误
5. 最佳实践
Go语言错误处理的推荐实践:
- 优先返回`error`而不是使用`panic`(`panic`应仅用于程序无法继续运行的严重错误)
- 为公共API定义明确的错误类型(如`ErrInvalidInput`)
- 错误信息应包含足够上下文(如参数值、操作类型)
- 避免在循环中使用`defer`(可能导致资源未及时释放)
package api
import "errors"
var (
ErrInvalidInput = errors.New("无效输入")
ErrNotFound = errors.New("资源不存在")
)
func GetUser(id string) (*User, error) {
if id == "" {
return nil, ErrInvalidInput
}
// ... 其他逻辑
return nil, ErrNotFound
}