Go语言错误处理最佳实践

错误处理实践

我们在go语言中设计error的处理体系时候, 一般都会去做下面两点

  1. 直接使用errors.New()生成error接口的值
  2. 扩展error接口, 并定义扩展error接口的实现类型

error接口是什么?

go语言的error是一个接口类型, 其源码如下:

go 复制代码
type error interface {
	Error() string
}

我们可以定义它的实现类型, 比如我们经常使用到的errors.New()方法, 返回值为一个error接口的实现类型*errorString的结构体字面量

go 复制代码
package errors

func New(text string) error {
	return &errorString{text}
}

type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

//package main
xxxerr := errors.New("xxx")

所以我们可以直接调用errors.New()为我们生成一个error接口的值

扩展的error接口

我们为什么需要对error接口进行扩展呢?, 原因是error的实现类型范围太大了, 细粒度不够小, 所以我们需要实现更加精细的控制, 关于这种设计我们可以参考go语言标准库中的一些error处理代码, 比如下面的net.Error

go 复制代码
type Error interface {
	//嵌入了error接口, 实现net.Error也会实现error
    error

    //扩展
    Timeout() bool
	Temporary() bool
}

然后我们又可以定义一个类型来实现这个扩展错误接口类型, 比如下面这个OpError:

go 复制代码
type OpError struct {
	Op string
	Net string
	Source Addr
	Addr Addr
	Err error
}

//实现函数1
func (e *OpError) Error() string {
	return ""
}

//下面是实现函数2
func (e *OpError) Timeout() bool {
	//对应的处理逻辑
    return true
}

//下面是实现函数2
func (e *OpError) Temporary() bool {}

我们发现该结构体中存在一个名字叫做Err的类型为error的字段, 它代表了该错误的潜在错误, 有可能OpError类型的错误值还包含了AddrError这种错误

通过这种类型建立起树形的错误体系, 用统一字段建立可追溯的链式错误关联, 我们就可以建立起来一套优秀的错误处理机制

为了更好的表示, 我画了一张图

具体的错误

因为Go语言的error是一个接口, 所以这个它的值的实际类型是非常复杂的, 于是我们就需要去判断它的值的一个实际类型

  1. 如果错误值在某一个范围内, 我们可以使用类型断言表达式 或者类型断言+switch语句进行判断
  2. 对于已有相应变量且类型相同的一系列错误值, 一般直接使用判等操作 + switch语句
  3. 没有相应变量且类型未知的一系列错误值, 只能使用其错误信息的字符串表示形式来判断

下面我们分别来看上面的内容: 首先是第一点, 已知错误值的范围比如: {os.PathError|os.LinkError|os.SyscallError|exec.Error}, 是它们中的一个, 我们可以直接使用类型断言+switch, 然后返回潜在错误类型

go 复制代码
func underlyingError(err error) error {
  switch err := err.(type) {
  case *os.PathError:
    return err.Err
  case *os.LinkError:
    return err.Err
  case *os.SyscallError:
    return err.Err
  case *exec.Error:
    return err.Err
  }
  return err
}

当我已经知道某个错误是哪一个, 我们直接使用判等操作+switch,

go 复制代码
printError := func(i int, err error) {
    if err == nil {
        fmt.println("nil error")
        return
    }

    err = underlyingError(err)

    switch err {  
    case os.ErrClosed:    
        fmt.Printf("error(closed)[%d]: %s\n", i, err)  
    case os.ErrInvalid:    
        fmt.Printf("error(invalid)[%d]: %s\n", i, err)  
    case os.ErrPermission:    
        fmt.Printf("error(permission)[%d]: %s\n", i, err)  
    }
}

通过上面这种直接判等操作, 我们就可以锁定具体的错误值了

对于上面两种情况, 我们都会有比较明确的方法去解决, 但是我们对一个错误值可能代表的含义知道的很少, 那么就只能通过错误信息去判断了

相关推荐
用户743835613515 小时前
无锁 Hub:我的 IM 系统为什么用 channel 而不是 mutex 管理在线用户
go
吴佳浩1 天前
Go史上最大“打脸”现场来了:泛型方法终于实现了
后端·go
明月_清风1 天前
深入 Go 并发编程:从 Goroutine 到 Channel 的系统性避坑指南
后端·go
用户34232323763172 天前
开源!Go+Wails+Vue3 手搓一个 PLC 实时监控桌面工具
go
止语Lab2 天前
为什么你的 Go TCP server P99 延迟这么高
go
Andy Dennis3 天前
nsq学习记录
消息队列·go·nsq
韦胖漫谈IT3 天前
选语言不是站队,是选适合问题的工具
java·python·ai·rust·go·技术落地
喵个咪3 天前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
夜悊3 天前
Go网络编程的学习代码示例:客户端/服务端(C/S)模型
go