golang Error的一些坑

golang Error的一些坑

golang error的设计可能是被人吐槽最多的golang设计了。

最经典的err!=nil只影响代码风格设计,而有一些坑会导致我们的程序发生一些与我们预期不符的问题,开发过程中需要注意。

errors.Is​判断error是否Wrap不符合预期

errors.Is​经常用于判断当前的的error(第一个参数)是否包含target error(第二个参数),用于替代==​判断当前err与target err的关系,like:

golang 复制代码
const badInput = "abc"

var ErrBadInput = errors.New("bad input")

func validateInput(input string) error {
    if input == badInput {
        return fmt.Errorf("validateInput: %w", ErrBadInput)
    }
    return nil
}

func main() {
    input := badInput

    err := validateInput(input)
    if errors.Is(err, ErrBadInput) {
        fmt.Println("bad input error")
    }
}

结果输出:bad input error​ ,符合预期。

但是我们再来看看下面代码:

golang 复制代码
func main() {
	errMsg := "123"
	fmt.Println(errors.Is(errors.New(errMsg), errors.New(errMsg))) //结果反直觉,结果为false

	err123 := errors.New(errMsg)
	fmt.Println(errors.Is(err123, err123)) //结果为true
}

我们使用相同的errMsg初始化了两个err,使用errors.Is​比较,结果为false。

原因是errors.Is​虽然可以不断的拆包unwarp,但是在不能继续unwarp的时候,会用err == Error 直接比较错误对象与指定的错误对象,也就是指针的对比,肯定就对比不通过了。

这样很容易踩坑。

虽然erros.Is是会自动帮unwarp的,但是底层本质上还是指针的对比,new出来的err肯定是不同的err。

经验教训:

  • 不要使用erros.New的方式来判断error是否相等或包含,如果非要使用就用error()把error信息打出来来比较吧。

  • 为什么常能看见库设计者是直接实例化了一个public​的error对象来提供给你使用,而不是将其设置为private让你自己去new一个来判断(设计成private之后使用者就必须使用errors.Err()打印出msg来判断是否相等了,实在太蠢),like:

如果不了解erros.Is的使用可以参考:https://gosamples.dev/check-error-type/

自定义error判断是否为nil不符合预期

见代码:

golang 复制代码
type CustomizedError struct {
}

func (c CustomizedError) Error() string {
	return "CustomizedError"
}

func func1() error {
	var err *CustomizedError = nil
	return err
}

func main() {
	err := func1()
	if err != nil {
		log.Fatalf("1 err: %v", err) // 不符合预期,以为 err 为nil,但是会进这个分支
	}
}

上面判断不符合预期的原因是error类型是一个接口Interface,interface 设计为了两部分:

  • type
  • value

其中,value 由一个任意的具体值表示,称作 interface 的 dynamic value ;而 type 则对应该 value 的类型(即 dynamic type);例如++对于对于 var a int = 3来说,把a 赋值给interface时, interface是使用(int, 3)进行存储的++。

因此当想判断 interface 的值为 nil​时 ,则必须是其内部 value 和 type 均未设置的情况,即 (nil, nil)​ ;

在上面的代码案例中func1()​中返回值err的type已经不是nil了,因此后续会判断不通过。

禁言教训:

  • 自定义error的时候禁止在任何地方出现dynamic value为nil的error,like:var err *CustomizedError = nil,要么直接返回nil,要么初始化一个不为nil的返回。
  • 延续上一条:如果自己是库的提供者,因此避免暴露自定义的error类型,而是只提供 NewMyError() error 函数,避免使用方产生误用。like:
golang 复制代码
type myErr struct { // 注意这里的设计,myErr没有暴露出去
   code int
   msg  string
}

func (e myErr) Error() string {   
   return fmt.Sprintf("code:%d,msg:%v", e.code, e.msg)
}

func New(code int, msg string) error {// 注意这里的设计,myErr没有暴露出去,只提供一个返回error的初始化方法
   return myErr{
      code: code,
      msg:  msg,
   }
}

func GetCode(err error) int {
   if e, ok := err.(myErr); ok {
      return e.code
   }
   return -1
}

func GetMsg(err error) string {
   if e, ok := err.(myErr); ok {
      return e.msg
   }
   return ""
}

参考资料:

https://coolshell.cn/articles/21140.html

https://juejin.cn/post/6974037920567017509

相关推荐
程序员鱼皮1 小时前
我代表编程导航,向大家道歉!
前端·后端·程序员
zjjuejin1 小时前
Maven 生命周期与插件机制
后端·maven
阿杆2 小时前
为什么我建议你把自建 Redis 迁移到云上进行托管
redis·后端
Java水解2 小时前
go语言教程(全网最全,持续更新补全)
后端·go
bobz9652 小时前
QEMU 使用 DPDK 时候在 libvirt xml 中设置 sock 的目的
后端
thinktik2 小时前
AWS EKS 计算资源自动扩缩之按需申请Fargate[AWS 中国宁夏区]
后端·aws
thinktik3 小时前
AWS EKS 实现底层EC2计算资源的自动扩缩[AWS 中国宁夏区]
后端·aws
uhakadotcom3 小时前
什么是OpenTelemetry?
后端·面试·github
知其然亦知其所以然3 小时前
MySQL 社招必考题:如何优化特定类型的查询语句?
后端·mysql·面试
用户4099322502123 小时前
给接口加新字段又不搞崩老客户端?FastAPI的多版本API靠哪三招实现?
后端·ai编程·trae