Go項(xiàng)目實(shí)戰(zhàn)-讓自定義Error支持Go的errors.Is判定以及原型模式的應(yīng)用
經(jīng)過前面三節(jié)高代碼強(qiáng)度的學(xué)習(xí),相信大家都已經(jīng)有點(diǎn)累了,本節(jié)我們不著急繼續(xù)“趕路”,休息片刻!我們換個(gè)輕松點(diǎn)的話題,聊一聊咱們項(xiàng)目定制化Error--AppError 怎么支持Go語言的 errors.Is 判定,以及項(xiàng)目預(yù)定義的那些Error在實(shí)際使用過程中某些情況下會(huì)出現(xiàn)循環(huán)引用的問題,我們會(huì)利用一個(gè)原型設(shè)計(jì)模式來解決這個(gè)問題。
項(xiàng)目定制化Error 回顧
在定義項(xiàng)目 Error 實(shí)現(xiàn)錯(cuò)誤鏈和發(fā)生位置記錄這篇文章中我們給項(xiàng)目定義了自己的Error類型 AppError
type AppError struct {
code int `json:"code"`
msg string `json:"msg"`
cause error `json:"cause"`
occurred string `json:"occurred"`
}
目的是為了通過自己的封裝讓Go的Error能支持錯(cuò)誤原因和發(fā)生位置的記錄。同時(shí)我們還為項(xiàng)目的開發(fā)預(yù)定義了很多Error變量。
// 用戶模塊相關(guān)錯(cuò)誤碼 10000100 ~ 1000199
var (
ErrUserInvalid = newError(10000101, "用戶異常")
ErrUserNameOccupied = newError(10000102, "用戶名已被占用")
ErrUserNotRight = newError(10000103, "用戶名或密碼不正確")
)
// 商品模塊相關(guān)錯(cuò)誤碼 10000200 ~ 1000299
var (
ErrCommodityNotExists = newError(10000200, "商品不存在")
ErrCommodityStockOut = newError(10000201, "庫存不足")
)
// 購物車模塊相關(guān)錯(cuò)誤碼 10000300 ~ 1000399
var (
ErrCartItemParam = newError(10000300, "購物項(xiàng)參數(shù)異常")
ErrCartWrongUser = newError(10000301, "用戶購物信息不匹配")
)
在 Go項(xiàng)目Error的統(tǒng)一管理和處理建議 中我建議需要返回給客戶端返回約定好的錯(cuò)誤碼的錯(cuò)誤都在這里預(yù)先定義。而對(duì)于一些像DB、存儲(chǔ)之類錯(cuò)誤產(chǎn)生的Error 無需告知客戶端明確原因只需要讓客戶端發(fā)生了服務(wù)端內(nèi)部錯(cuò)誤的情況、或者不知道怎么處理的底層錯(cuò)誤,我們先統(tǒng)一用Wrap方法把它包裝成應(yīng)用的AppError。
err = DBMaster().WithContext(ud.ctx).Create(userModel).Error
if err != nil {
err = errcode.Wrap("UserDaoCreateUserError", err)
return nil, err
}
在設(shè)計(jì)的過程中,覺得已經(jīng)夠全面了,但是只要真正把它來開發(fā)需求時(shí),還是能發(fā)現(xiàn)有問題的,具體什么問題呢? 我們繼續(xù)往下看。
怎么讓自定義Error支持Go的errors.Is判定
想讓Error支持Go的errors.Is 判定,我們先來看看errors.Is 方法里到底是怎么判定Error是不是給定的目標(biāo)錯(cuò)誤的,其源碼如下:
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
}
}
}
其源碼中邏輯是會(huì)一層層地解包error,每層的error都去看看是否已經(jīng)實(shí)現(xiàn)了 interface{ Is(error) bool } 這個(gè)接口,如果實(shí)現(xiàn)了調(diào)用該層error的Is方法進(jìn)行判定,如果跟給定error不相等或者沒有實(shí)現(xiàn)Is接口,則繼續(xù)用Uwrap解包,解到不能解為止 (err == nil)。
所以這里給我們預(yù)留了兩個(gè)接口,我們讓AppError實(shí)現(xiàn) Is 和 Uwrap 方法后,它也就支持Go的 errors.Is 判定啦。
下面我們?cè)?common/errcode/error.go 中加入這兩個(gè)方法的實(shí)現(xiàn)。
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()
}
關(guān)于項(xiàng)目自定義Error的優(yōu)化,在課程中我還使用了這里使用設(shè)計(jì)模式里的原型模式, 把項(xiàng)目預(yù)定義的全局錯(cuò)誤都是當(dāng)作原型-prototype,保證我們既能規(guī)范管理我們項(xiàng)目的錯(cuò)誤碼,也能更自由放心地在程序中使用它們。