Go错误处理与接口设计
Go错误基于接口设计,灵活且可扩展。
error接口定义
标准error接口
Go
// 标准库定义
type error interface {
Error() string
}
// 任何实现Error()方法的类型都是error
type MyError struct {
Msg string
}
func (e MyError) Error() string {
return e.Msg
}
var err error = MyError{Msg: "出错了"}
fmt.Println(err.Error())
自定义错误类型
结构体错误
Go
type ValidationError struct {
Field string
Value interface{}
Msg string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("验证失败: %s=%v, %s", e.Field, e.Value, e.Msg)
}
// 使用
func validate(name string) error {
if name == "" {
return ValidationError{
Field: "name",
Value: name,
Msg: "不能为空",
}
}
return nil
}
包装错误(Go 1.13+)
Go
import "errors"
// 创建错误
err := errors.New("基础错误")
// 包装错误
wrapped := fmt.Errorf("操作失败: %w", err)
// 获取原始错误
original := errors.Unwrap(wrapped)
错误检查
errors.Is检查类型
Go
var ErrNotFound = errors.New("未找到")
func find(id int) error {
return ErrNotFound
}
func main() {
err := find(1)
if errors.Is(err, ErrNotFound) {
fmt.Println("记录不存在")
}
}
errors.As检查类型并提取
Go
type TimeoutError struct {
Duration time.Duration
}
func (e TimeoutError) Error() string {
return fmt.Sprintf("超时: %v", e.Duration)
}
func main() {
err := TimeoutError{Duration: 5 * time.Second}
var te TimeoutError
if errors.As(err, &te) {
fmt.Println("超时时间:", te.Duration)
}
}
接口设计中的错误处理
返回错误而非panic
Go
// 不推荐:panic
func createUser(name string) User {
if name == "" {
panic("名字不能为空")
}
return User{Name: name}
}
// 推荐:返回错误
func createUser(name string) (User, error) {
if name == "" {
return User{}, ValidationError{Field: "name", Msg: "不能为空"}
}
return User{Name: name}, nil
}
错误作为接口返回
Go
type Repository interface {
Find(id int) (*User, error)
Save(user *User) error
Delete(id int) error
}
// 实现者返回具体错误类型
type MySQLRepo struct{}
func (r MySQLRepo) Find(id int) (*User, error) {
// 返回具体错误
return nil, ErrNotFound
}
错误处理模式
检查并处理
Go
func process() error {
user, err := repo.Find(1)
if err != nil {
if errors.Is(err, ErrNotFound) {
// 特定错误处理
return fmt.Errorf("用户不存在: %w", err)
}
// 其他错误
return fmt.Errorf("查找失败: %w", err)
}
err = repo.Save(user)
if err != nil {
return fmt.Errorf("保存失败: %w", err)
}
return nil
}
多层错误包装
Go
func service() error {
if err := repo.Find(1); err != nil {
return fmt.Errorf("服务层: %w", err)
}
return nil
}
func handler() error {
if err := service(); err != nil {
return fmt.Errorf("处理器层: %w", err)
}
return nil
}
// 最终错误包含完整调用链
// 处理器层: 服务层: 用户不存在: 未找到
错误类型设计对比
| 方式 | 用途 | 示例 |
|---|---|---|
| errors.New | 简单错误 | errors.New("失败") |
| fmt.Errorf | 格式化错误 | fmt.Errorf("失败: %d", id) |
| 自定义结构体 | 携带上下文 | ValidationError{Field:"name"} |
| %w包装 | 保留调用链 | fmt.Errorf("失败: %w", err) |
| errors.Is | 检查类型 | errors.Is(err, ErrNotFound) |
| errors.As | 提取信息 | errors.As(err, &te) |
要点总结
- error接口只需实现Error()方法
- 自定义错误类型可携带更多上下文
- 使用%w包装错误保留调用链
- errors.Is检查特定错误类型
- errors.As提取自定义错误信息
- 接口返回error而非panic
- 每层包装错误增加上下文
- 错误处理是接口设计的重要部分
📝 发现内容有误?点击此处直接编辑