Go项目实战-让自定义Error支持Go的errors.Is判定以及原型模式的应用

开发 项目管理
关于项目自定义Error的优化,在课程中我还使用了这里使用设计模式里的原型模式, 把项目预定义的全局错误都是当作原型-prototype,保证我们既能规范管理我们项目的错误码,也能更自由放心地在程序中使用它们。​

经过前面三节高代码强度的学习,相信大家都已经有点累了,本节我们不着急继续“赶路”,休息片刻!我们换个轻松点的话题,聊一聊咱们项目定制化Error--AppError 怎么支持Go语言的 errors.Is 判定,以及项目预定义的那些Error在实际使用过程中某些情况下会出现循环引用的问题,我们会利用一个原型设计模式来解决这个问题。

图片

项目定制化Error 回顾

在定义项目 Error 实现错误链和发生位置记录这篇文章中我们给项目定义了自己的Error类型 AppError

type AppError struct {
 code  int    `json:"code"`
 msg   string `json:"msg"`
 cause error  `json:"cause"`
 occurred string `json:"occurred"`
}

目的是为了通过自己的封装让Go的Error能支持错误原因和发生位置的记录。同时我们还为项目的开发预定义了很多Error变量。

// 用户模块相关错误码 10000100 ~ 1000199
var (
 ErrUserInvalid      = newError(10000101, "用户异常")
 ErrUserNameOccupied = newError(10000102, "用户名已被占用")
 ErrUserNotRight     = newError(10000103, "用户名或密码不正确")
)
// 商品模块相关错误码 10000200 ~ 1000299
var (
 ErrCommodityNotExists = newError(10000200, "商品不存在")
 ErrCommodityStockOut  = newError(10000201, "库存不足")
)
// 购物车模块相关错误码 10000300 ~ 1000399
var (
 ErrCartItemParam = newError(10000300, "购物项参数异常")
 ErrCartWrongUser = newError(10000301, "用户购物信息不匹配")
)

在 Go项目Error的统一管理和处理建议 中我建议需要返回给客户端返回约定好的错误码的错误都在这里预先定义。而对于一些像DB、存储之类错误产生的Error 无需告知客户端明确原因只需要让客户端发生了服务端内部错误的情况、或者不知道怎么处理的底层错误,我们先统一用Wrap方法把它包装成应用的AppError。

err = DBMaster().WithContext(ud.ctx).Create(userModel).Error
if err != nil {
    err = errcode.Wrap("UserDaoCreateUserError", err)
    return nil, err
}

在设计的过程中,觉得已经够全面了,但是只要真正把它来开发需求时,还是能发现有问题的,具体什么问题呢? 我们继续往下看。

怎么让自定义Error支持Go的errors.Is判定

想让Error支持Go的errors.Is 判定,我们先来看看errors.Is 方法里到底是怎么判定Error是不是给定的目标错误的,其源码如下:

func Is(err, target error) bool {
 if target == nil {
  return err == target
 }

 isComparable := reflectlite.TypeOf(target).Comparable()
 for {
  if isComparable && err == target {
   return true
  }
  if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
   return true
  }
  if err = Unwrap(err); err == nil {
   return false
  }
 }
}

其源码中逻辑是会一层层地解包error,每层的error都去看看是否已经实现了 interface{ Is(error) bool } 这个接口,如果实现了调用该层error的Is方法进行判定,如果跟给定error不相等或者没有实现Is接口,则继续用Uwrap解包,解到不能解为止 (err == nil)。

所以这里给我们预留了两个接口,我们让AppError实现 Is 和 Uwrap 方法后,它也就支持Go的 errors.Is 判定啦。

下面我们在 common/errcode/error.go 中加入这两个方法的实现。

package errcode

func (e *AppError) UnWrap() error {
 return e.cause
}

// Is 与上面的UnWrap一起让 *AppError 支持 errors.Is(err, target)
func (e *AppError) Is(target error) bool {
 targetErr, ok := target.(*AppError)
 if !ok {
  return false
 }
 return targetErr.Code() == e.Code()
}

关于项目自定义Error的优化,在课程中我还使用了这里使用设计模式里的原型模式, 把项目预定义的全局错误都是当作原型-prototype,保证我们既能规范管理我们项目的错误码,也能更自由放心地在程序中使用它们。

责任编辑:武晓燕 来源: 网管叨bi叨
相关推荐

2022-09-21 08:47:05

项目多线程对象

2023-06-19 08:49:55

go文件管理

2020-10-21 14:29:15

原型模式

2021-10-28 19:09:09

模式原型Java

2021-05-18 08:52:31

Prototype 原型模式设计模式

2022-05-30 08:14:48

编译器Go语言

2013-11-26 17:00:08

Android设计模式

2021-09-08 09:41:09

开发Go代码

2015-06-08 09:05:10

Java原型模式

2024-11-13 09:13:45

2011-05-27 09:51:44

Windows Pho应用商店

2024-08-09 10:59:01

KubernetesSidecar模式

2022-11-06 23:17:23

Go语言项目

2024-12-27 15:10:16

设计模式原型模式场景

2016-08-18 13:56:33

AndroidExecutorsubmit

2023-03-13 00:10:46

Go语言版本

2021-06-07 09:51:22

原型模式序列化

2009-08-21 15:38:45

ControllerF

2014-07-15 11:16:17

Go语言

2023-12-29 08:10:41

Go并发开发
点赞
收藏

51CTO技术栈公众号