⚠️ 第06课:错误处理

Go基础 第1阶段·第6/7课

📋 本课目标

🔬 代码演示与验证

package main
import ("errors";"fmt";"os";"strconv")
type ValidationError struct{Field string;Message string}
func(e *ValidationError)Error()string{return fmt.Sprintf("验证错误[%s]:%s",e.Field,e.Message)}
func validateAge(age int)error{
    if age<0{return &ValidationError{Field:"age",Message:"年龄不能为负"}}
    if age>150{return &ValidationError{Field:"age",Message:"年龄不合理"}}
    return nil
}
func readConfig(path string)error{_,err:=os.ReadFile(path);if err!=nil{return fmt.Errorf("读取配置失败:%w",err)};return nil}
var(ErrNotFound=errors.New("资源未找到");ErrUnauthorized=errors.New("未授权"))
func getResource(id int)error{if id<=0{return ErrNotFound};if id==999{return ErrUnauthorized};return nil}
func safeDivide(a,b int)(result int,err error){
    defer func(){if r:=recover();r!=nil{err=fmt.Errorf("panic:%v",r)}}()
    return a/b,nil
}
func main() {
    fmt.Println("=== 基本错误 ===")
    _,err:=strconv.Atoi("abc");if err!=nil{fmt.Printf("转换失败:%v\n",err)}
    fmt.Println("\n=== 自定义错误 ===")
    if err:=validateAge(-5);err!=nil{if ve,ok:=err.(*ValidationError);ok{fmt.Printf("字段:%s 消息:%s\n",ve.Field,ve.Message)}}
    fmt.Println("\n=== 错误包装 ===")
    err=readConfig("nonexistent.yaml");if err!=nil{fmt.Printf("错误:%v\n",err)}
    fmt.Println("\n=== 哨兵错误 ===")
    if err:=getResource(-1);err!=nil{if errors.Is(err,ErrNotFound){fmt.Println("→资源不存在")}}
    fmt.Println("\n=== panic/recover ===")
    if _,err:=safeDivide(10,0);err!=nil{fmt.Printf("除法错误:%v\n",err)}
    fmt.Println("\n=== 多错误收集 ===")
    var errs[]error
    for i:=0;i<5;i++{if err:=validateAge(i*40);err!=nil{errs=append(errs,err)}}
    fmt.Printf("收集%d个错误\n",len(errs))
}

🔍 运行验证

✅ 验证通过 — 实机运行结果:

=== 基本错误 ===
转换失败:strconv.Atoi: parsing "abc": invalid syntax

=== 自定义错误 ===
字段:age 消息:年龄不能为负

=== 错误包装 ===
错误:读取配置失败:open nonexistent.yaml: no such file or directory

=== 哨兵错误 ===
→资源不存在

=== panic/recover ===
除法错误:panic:runtime error: integer divide by zero

=== 多错误收集 ===
收集1个错误

🧠 错误处理哲学

Go: if err != nil { ... } — 显式处理 Java: try/catch — 隐式跳转 核心理念:错误是值,必须显式处理

🔧 错误包装

originalErr := errors.New("连接超时")
wrappedErr := fmt.Errorf("DB操作失败: %w", originalErr)
errors.Is(wrappedErr, originalErr)  // true
errors.As(wrappedErr, &target)     // 类型匹配

💥 panic/recover

// recover在defer中捕获
defer func() {
    if r := recover(); r != nil {
        log.Printf("panic: %v", r)
    }
}()

🎯 最佳实践

  1. 总是检查错误
  2. 尽早返回减少嵌套
  3. 添加上下文 %w
  4. 自定义错误类型
  5. 不要吞掉错误

🔑 本课要点回顾

知识点关键内容
error接口Error() string
自定义错误结构体+Error()
错误包装fmt.Errorf+%w
哨兵错误var ErrX=errors.New()
panic/recover仅不可恢复

🏋️ 练习

错误层次

AppError/NotFoundError

错误收集器

Collect/HasErrors

panic中间件

HTTP 500恢复

🏆

成就解锁:错误处理专家!

📖 错误处理深入

错误包装与解包

// 包装错误链
err := fmt.Errorf("查询用户失败: %w",
    fmt.Errorf("数据库连接失败: %w",
        errors.New("连接超时")))

// errors.Is — 检查错误链
errors.Is(err, ErrTimeout)  // true

// errors.As — 提取特定类型
var netErr *net.OpError
errors.As(err, &netErr)  // true

// errors.Unwrap — 解开一层
unwrapped := errors.Unwrap(err)

自定义错误最佳实践

type AppError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Err     error  `json:"-"`
}

func (e *AppError) Error() string { return e.Message }
func (e *AppError) Unwrap() error { return e.Err }

// HTTP错误码映射
func (e *AppError) StatusCode() int {
    switch e.Code {
    case 1001: return http.StatusNotFound
    case 1002: return http.StatusBadRequest
    default:   return http.StatusInternalServerError
    }
}

📖 延伸阅读与资源

🎯 实战开发建议

  1. 动手编码 — 只看不练等于没学,每课代码必须自己敲一遍
  2. 使用go docgo doc fmt.Printf 查看标准库文档
  3. 编写测试go test 验证你的理解是否正确
  4. 阅读源码 — 标准库是Go最佳实践的活教材
  5. 项目实战 — 在真实项目中应用所学

⚡ Go性能优化清单

// 1. 预分配切片容量 — 避免频繁扩容
s := make([]int, 0, expectedSize)

// 2. strings.Builder拼接 — 比fmt.Sprintf快3-5倍
var b strings.Builder
b.WriteString("hello")
b.WriteString(" world")
result := b.String()

// 3. sync.Pool对象复用 — 减少GC压力
var bufPool = sync.Pool{
    New: func() interface{} { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)

// 4. 减少堆分配 — 小对象栈分配,避免逃逸
// 用 go build -gcflags="-m" 分析逃逸

// 5. 使用benchmark验证性能
func BenchmarkXxx(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // 被测代码
    }
}
// go test -bench=. -benchmem

🔍 调试与排查技巧

// 1. 格式化调试输出
fmt.Printf("%+v
", myStruct)  // 带字段名
fmt.Printf("%#v
", myValue)   // Go语法表示

// 2. log包运行时日志
log.Printf("[DEBUG] value=%v", val)
log.SetFlags(log.LstdFlags | log.Lshortfile)

// 3. delve源码调试器
// 安装: go install github.com/go-delve/delve/cmd/dlv@latest
// 使用: dlv debug ./main.go
// 命令: break main.main / continue / next / print

// 4. pprof性能分析
import _ "net/http/pprof"
go http.ListenAndServe(":6060", nil)
// 访问: http://localhost:6060/debug/pprof/
// 命令: go tool pprof http://localhost:6060/debug/pprof/profile

// 5. race detector竞态检测
// go run -race main.go
// go test -race ./...

📊 Go工具生态

工具用途安装
golangci-lint代码静态检查go install github.com/golangci/golangci-lint/cmd/...@latest
gofumpt代码格式化(增强)go install mvdan.cc/gofumpt@latest
goimports自动管理importgo install golang.org/x/tools/cmd/goimports@latest
dlv源码调试器go install github.com/go-delve/delve/cmd/dlv@latest
air热重载开发go install github.com/cosmtrek/air@latest
swagSwagger文档生成go install github.com/swaggo/swag/cmd/swag@latest
migrate数据库迁移go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

🏗️ Go项目结构最佳实践

myproject/
├── cmd/                    # 可执行程序入口
│   └── server/
│       └── main.go
├── internal/               # 私有代码(不可外部导入)
│   ├── handler/            # HTTP处理
│   ├── service/            # 业务逻辑
│   ├── repository/         # 数据访问
│   └── model/              # 数据模型
├── pkg/                    # 可复用公共包
│   └── utils/
├── api/                    # API定义(proto/openapi)
├── configs/                # 配置文件
├── scripts/                # 构建/部署脚本
├── test/                   # 集成测试
├── go.mod
├── go.sum
├── Makefile
└── Dockerfile

🌐 Go Web开发生态

类别推荐方案替代方案
Web框架GinEcho / Chi / Fiber
ORMGORMEnt / sqlx / sqlc
配置Viperkoanf
日志zapzerolog / slog
验证validatorgo-playground/validator
缓存go-redisredigo
JWTgolang-jwt
Swaggerswag
测试testify
Mockgomocktestify/mock

💡 编码规范

// 命名规范
// 包名: 小写单词, 不用下划线  package httputil
// 导出: 大写开头              func CreateUser()
// 未导出: 小写开头            func validateInput()
// 接口: -er后缀              type Reader interface{}
// 常量: 驼峰                 const maxRetries = 3
// 枚举: 类型+常量            type Role int; const Admin Role = 0

// 错误处理规范
// 错误变量: Err前缀           var ErrNotFound = errors.New("...")
// 错误类型: Error后缀         type ValidationError struct{...}
// 包装: %w                    fmt.Errorf("查询失败: %w", err)

// 注释规范
// 导出符号必须有注释
// 注释以符号名开头            // CreateUser creates a new user.
// 包注释: doc.go              // Package user handles user operations.