错误处理
Go语言使用显式的错误处理机制,不使用异常。
error类型
基本概念
error 是一个内置接口:
go
type error interface {
Error() string
}创建错误
go
import "errors"
// 使用errors.New创建简单错误
err := errors.New("出错了")
// 使用fmt.Errorf创建格式化错误
err := fmt.Errorf("用户 %s 不存在", username)错误处理模式
基本模式
go
package main
import (
"errors"
"fmt"
)
// 返回错误的函数
func divide(a, b float64) (float64, 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)
return
}
fmt.Printf("结果: %.2f\n", result)
// 测试错误情况
_, err = divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
}
}提前返回
go
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("读取文件失败: %w", err)
}
// 处理数据...
return nil
}自定义错误
自定义错误类型
go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证错误 [%s]: %s", e.Field, e.Message)
}
func validateUser(name string, age int) error {
if name == "" {
return &ValidationError{
Field: "name",
Message: "姓名不能为空",
}
}
if age < 0 || age > 150 {
return &ValidationError{
Field: "age",
Message: "年龄必须在0-150之间",
}
}
return nil
}
func main() {
err := validateUser("", 25)
if err != nil {
fmt.Println(err)
// 类型断言获取详细信息
if ve, ok := err.(*ValidationError); ok {
fmt.Printf("字段: %s, 消息: %s\n", ve.Field, ve.Message)
}
}
}错误包装
使用 %w 包装错误
go
func readFile(path string) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("打开文件 %s 失败: %w", path, err)
}
defer file.Close()
return nil
}
func main() {
err := readFile("nonexistent.txt")
fmt.Println(err) // 打开文件 nonexistent.txt 失败: open nonexistent.txt: 系统找不到指定的文件。
}解包错误
go
import "errors"
func main() {
err := readFile("nonexistent.txt")
// 使用errors.Is检查错误链
if errors.Is(err, os.ErrNotExist) {
fmt.Println("文件不存在")
}
// 使用errors.As获取特定类型错误
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("路径:", pathErr.Path)
}
}panic和recover
panic
panic 用于不可恢复的错误:
go
func mustPositive(n int) int {
if n < 0 {
panic("数值必须为正数")
}
return n
}
func main() {
fmt.Println(mustPositive(5)) // 正常
fmt.Println(mustPositive(-1)) // panic
fmt.Println("这行不会执行")
}recover
recover 用于捕获panic:
go
func safeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
}
}()
panic("出错了")
}
func main() {
safeOperation()
fmt.Println("程序继续执行")
}实际应用
go
func httpHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Println("处理请求时发生panic:", err)
http.Error(w, "内部服务器错误", 500)
}
}()
// 处理请求...
}错误处理最佳实践
1. 不要忽略错误
go
// 错误做法
file, _ := os.Open("file.txt")
// 正确做法
file, err := os.Open("file.txt")
if err != nil {
return err
}2. 提供上下文信息
go
// 错误做法
return err
// 正确做法
return fmt.Errorf("处理用户 %s 时出错: %w", username, err)3. 使用自定义错误类型
go
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s (ID: %s) 不存在", e.Resource, e.ID)
}
func (e *NotFoundError) Is(target error) bool {
t, ok := target.(*NotFoundError)
if !ok {
return false
}
return e.Resource == t.Resource
}4. 错误变量定义
go
// 可导出的错误变量
var (
ErrNotFound = errors.New("资源不存在")
ErrUnauthorized = errors.New("未授权")
ErrBadRequest = errors.New("请求参数错误")
)
// 使用errors.Is检查
if errors.Is(err, ErrNotFound) {
// 处理不存在的情况
}错误处理示例
数据库操作
go
type User struct {
ID int
Name string
}
func GetUser(id int) (*User, error) {
row := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id)
var user User
err := row.Scan(&user.ID, &user.Name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, &NotFoundError{Resource: "User", ID: fmt.Sprint(id)}
}
return nil, fmt.Errorf("查询用户失败: %w", err)
}
return &user, nil
}配置文件读取
go
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %w", err)
}
if config.Host == "" {
return nil, &ValidationError{Field: "host", Message: "不能为空"}
}
return &config, nil
}