




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
第一篇文章帶你輕松搞懂Golang的error處理目錄Golang中的errorerror的幾種玩法哨兵錯(cuò)誤自定義錯(cuò)誤類型WraperrorGolang1.13版本error的新特性errors.UnWrap()errors.Is()errors.As()error處理最佳實(shí)踐優(yōu)先處理error只處理error一次不要反復(fù)包裝error不透明的錯(cuò)誤處理簡化錯(cuò)誤處理bufio.scanerrWriter何時(shí)該用panic小補(bǔ)充總結(jié)
Golang中的error
Golang中的error就是一個(gè)簡單的接口類型。只要實(shí)現(xiàn)了這個(gè)接口,就可以將其視為一種error
typeerrorinterface{
Error()string
error的幾種玩法
翻看Golang源碼,能看到許多類似于下面的這兩種error類型
哨兵錯(cuò)誤
varEOF=errors.New("EOF")
varErrUnexpectedEOF=errors.New("unexpectedEOF")
varErrNoProgress=errors.New("multipleReadcallsreturnnodataorerror")
缺點(diǎn):
1.讓error具有二義性
error!=nil不再意味著一定發(fā)生了錯(cuò)誤
比如io.Reader返回io.EOF來告知調(diào)用者沒有更多數(shù)據(jù)了,然而這又不是一個(gè)錯(cuò)誤
2.在兩個(gè)包之間創(chuàng)建了依賴
如果你使用了io.EOF來檢查是否read完所有的數(shù)據(jù),那么代碼里一定會(huì)導(dǎo)入io包
自定義錯(cuò)誤類型
一個(gè)不錯(cuò)的例子是os.PathError,它的優(yōu)點(diǎn)是可以附帶更多的上下文信息
typePathErrorstruct{
Opstring
Pathstring
Errerror
Wraperror
到這里我們可以發(fā)現(xiàn),Golang的error非常簡單,然而簡單也意味著有時(shí)候是不夠用的
Golang的error一直有兩個(gè)問題:
1.error沒有附帶file:line信息(也就是沒有堆棧信息)
比如這種error,鬼知道代碼哪一行報(bào)了錯(cuò),Debug時(shí)簡直要命
SERVICEERROR2025-03-25T16:32:10.687+0800!!!
Error1406:Datatoolongforcolumncontentatrow1
2.上層error想附帶更多日志信息時(shí),往往會(huì)使用fmt.Errorf(),fmt.Errorf()會(huì)創(chuàng)建一個(gè)新的error,底層的error類型就被吞掉了
varerrNoRows=errors.New("norows")
//模仿sql庫返回一個(gè)errNoRows
funcsqlExec()error{
returnerrNoRows
funcserviceNoErrWrap()error{
err:=sqlExec()
iferr!=nil{
returnfmt.Errorf("sqlExecfailed.Err:%v",err)
returnnil
funcTestErrWrap(t*testing.T){
//使用fmt.Errorf創(chuàng)建了一個(gè)新的err,丟失了底層err
err:=serviceNoErrWrap()
iferr!=errNoRows{
log.Println("=====errTypedon'tequalerrNoRows=====")
-------------------------------代碼運(yùn)行結(jié)果----------------------------------
===RUNTestErrWrap
2025/03/2617:19:43=====errTypedon'tequalerrNoRows=====
為了解決這個(gè)問題,我們可以使用/pkg/error包,使用errors.withStack()方法將err保
存到withStack對象
//withStack結(jié)構(gòu)體保存了error,形成了一條error鏈。同時(shí)*stack字段保存了堆棧信息。
typewithStackstruct{
error
*stack
也可以使用errors.Wrap(err,自定義文本),額外附帶一些自定義的文本信息
源碼解讀:先將err和message包進(jìn)withMessage對象,再將withMessage對象和堆棧信息包進(jìn)withStack對象
funcWrap(errerror,messagestring)error{
iferr==nil{
returnnil
err=withMessage{
cause:err,
msg:message,
returnwithStack{
err,
callers(),
Golang1.13版本error的新特性
Golang1.13版本借鑒了/pkg/error包,新增了如下函數(shù),大大增強(qiáng)了Golang語言判斷error類型的能力
errors.UnWrap()
//與errors.Wrap()行為相反
//獲取err鏈中的底層err
funcUnwrap(errerror)error{
u,ok:=err.(interface{
Unwrap()error
if!ok{
returnnil
returnu.Unwrap()
errors.Is()
在1.13版本之前,我們可以用err==targetErr判斷err類型
errors.Is()是其增強(qiáng)版:error鏈上的任一err==targetErr,即returntrue
//實(shí)踐:學(xué)習(xí)使用errors.Is()
varerrNoRows=errors.New("norows")
//模仿sql庫返回一個(gè)errNoRows
funcsqlExec()error{
returnerrNoRows
funcservice()error{
err:=sqlExec()
iferr!=nil{
returnerrors.WithStack(err)//包裝errNoRows
returnnil
funcTestErrIs(t*testing.T){
err:=service()
//errors.Is遞歸調(diào)用errors.UnWrap,命中err鏈上的任意err即返回true
iferrors.Is(err,errNoRows){
log.Println("=====errors.Is()succeeded=====")
//err經(jīng)errors.WithStack包裝,不能通過==判斷err類型
iferr==errNoRows{
log.Println("err==errNoRows")
-------------------------------代碼運(yùn)行結(jié)果----------------------------------
===RUNTestErrIs
2025/03/2518:35:00=====errors.Is()succeeded=====
例子解讀:
因?yàn)槭褂胑rrors.WithStack包裝了sqlError,sqlError位于error鏈的底層,上層的error已經(jīng)不再是sqlError類型,所以使用==無法判斷出底層的sqlError
源碼解讀:
我們很容易想到其內(nèi)部調(diào)用了err=Unwrap(err)方法來獲取error鏈中底層的error自定義error類型可以實(shí)現(xiàn)Is接口來自定義error類型判斷方法
funcIs(err,targeterror)bool{
iftarget==nil{
returnerr==target
isComparable:=reflectlite.TypeOf(target).Comparable()
for{
ifisComparableerr==target{
returntrue
//支持自定義error類型判斷
ifx,ok:=err.(interface{Is(error)bool});okx.Is(target){
returntrue
iferr=Unwrap(err);err==nil{
returnfalse
下面我們來看看如何自定義error類型判斷:
自定義的errNoRows類型,必須實(shí)現(xiàn)Is接口,才能使用erros.Is()進(jìn)行類型判斷
typeerrNoRowsstruct{
Descstring
func(eerrNoRows)Unwrap()error{returne}
func(eerrNoRows)Error()string{returne.Desc}
func(eerrNoRows)Is(errerror)bool{
returnreflect.TypeOf(err).Name()==reflect.TypeOf(e).Name()
//模仿sql庫返回一個(gè)errNoRows
funcsqlExec()error{
returnerrNoRows{"KaolengmianNB"}
funcservice()error{
err:=sqlExec()
iferr!=nil{
returnerrors.WithStack(err)
returnnil
funcserviceNoErrWrap()error{
err:=sqlExec()
iferr!=nil{
returnfmt.Errorf("sqlExecfailed.Err:%v",err)
returnnil
funcTestErrIs(t*testing.T){
err:=service()
iferrors.Is(err,errNoRows{}){
log.Println("=====errors.Is()succeeded=====")
-------------------------------代碼運(yùn)行結(jié)果----------------------------------
===RUNTestErrIs
2025/03/2518:35:00=====errors.Is()succeeded=====
errors.As()
在1.13版本之前,我們可以用if_,ok:=err.(targetErr)判斷err類型
errors.As()是其增強(qiáng)版:error鏈上的任一err與targetErr類型相同,即returntrue
//通過例子學(xué)習(xí)使用errors.As()
typesqlErrorstruct{
error
func(e*sqlError)IsNoRows()bool{
t,ok:=e.error.(ErrNoRows)
returnokt.IsNoRows()
typeErrNoRowsinterface{
IsNoRows()bool
//返回一個(gè)sqlError
funcsqlExec()error{
returnsqlError{}
//errors.WithStack包裝sqlError
funcservice()error{
err:=sqlExec()
iferr!=nil{
returnerrors.WithStack(err)
returnnil
funcTestErrAs(t*testing.T){
err:=service()
//遞歸使用errors.UnWrap,只要Err鏈上有一種Err滿足類型斷言,即返回true
sr:=sqlError{}
iferrors.As(err,sr){
log.Println("=====errors.As()succeeded=====")
//經(jīng)errors.WithStack包裝后,不能通過類型斷言將當(dāng)前Err轉(zhuǎn)換成底層Err
if_,ok:=err.(sqlError);ok{
log.Println("=====typeassertsucceeded=====")
----------------------------------代碼運(yùn)行結(jié)果--------------------------------------------
===RUNTestErrAs
2025/03/2518:09:02=====errors.As()succeeded=====
例子解讀:
因?yàn)槭褂胑rrors.WithStack包裝了sqlError,sqlError位于error鏈的底層,上層的error已經(jīng)不再是sqlError類型,所以使用類型斷言無法判斷出底層的sqlError
error處理最佳實(shí)踐
上面講了如何定義error類型,如何比較error類型,現(xiàn)在我們談?wù)勅绾卧诖笮晚?xiàng)目中做好error處理
優(yōu)先處理error
當(dāng)一個(gè)函數(shù)返回一個(gè)非空error時(shí),應(yīng)該優(yōu)先處理error,忽略它的其他返回值
只處理error一次
在Golang中,對于每個(gè)err,我們應(yīng)該只處理一次。
要么立即處理err(包括記日志等行為),returnnil(把錯(cuò)誤吞掉)。此時(shí)因?yàn)榘彦e(cuò)誤做了降級(jí),一定要小心處理函數(shù)返回值。
比如下面例子json.Marshal(conf)沒有returnerr,那么在使用buf時(shí)一定要小心空指針等錯(cuò)誤
要么returnerr,在上層處理err
反例:
//試想如果writeAll函數(shù)出錯(cuò),會(huì)打印兩遍日志
//如果整個(gè)項(xiàng)目都這么做,最后會(huì)驚奇的發(fā)現(xiàn)我們在處處打日志,項(xiàng)目中存在大量沒有價(jià)值的垃圾日志
//unabletowrite:io.EOF
//couldnotwriteconfig:io.EOF
typeconfigstruct{}
funcwriteAll(wio.Writer,buf[]byte)error{
_,err:=w.Write(buf)
iferr!=nil{
log.Println("unabletowrite:",err)
returnerr
returnnil
funcwriteConfig(wio.Writer,conf*config)error{
buf,err:=json.Marshal(conf)
iferr!=nil{
log.Printf("couldnotmarshalconfig:%v",err)
iferr:=writeAll(w,buf);err!=nil{
log.Println("countnotwriteconfig:%v",err)
returnerr
returnnil
不要反復(fù)包裝error
我們應(yīng)該包裝error,但只包裝一次
上層業(yè)務(wù)代碼建議Wraperror,但是底層基礎(chǔ)Kit庫不建議
如果底層基礎(chǔ)Kit庫包裝了一次,上層業(yè)務(wù)代碼又包裝了一次,就重復(fù)包裝了error,日志就會(huì)打重
比如我們常用的sql庫會(huì)返回sql.ErrNoRows這種預(yù)定義錯(cuò)誤,而不是給我們一個(gè)包裝過的error
不透明的錯(cuò)誤處理
在大型項(xiàng)目中,推薦使用不透明的錯(cuò)誤處理(Opaqueerrors):不關(guān)心錯(cuò)誤類型,只關(guān)心error是否為nil
好處:
耦合小,不需要判斷特定錯(cuò)誤類型,就不需要導(dǎo)入相關(guān)包的依賴。
不過有時(shí)候,這種處理error的方式不夠用,比如:業(yè)務(wù)需要對參數(shù)異常error類型做降級(jí)處理,打印Warn級(jí)別的日志
typeParamInvalidErrorstruct{
Descstring
func(eParamInvalidError)Unwrap()error{returne}
func(eParamInvalidError)Error()string{return"ParamInvalidError:"+e.Desc}
func(eParamInvalidError)Is(errerror)bool{
returnreflect.TypeOf(err).Name()==reflect.TypeOf(e).Name()
funcNewParamInvalidErr(descstring)error{
returnerrors.WithStack(ParamInvalidError{Desc:desc})
------------------------------頂層打印日志---------------------------------
iferrors.Is(err,Err.ParamInvalidError{}){
logger.Warnf(ctx,"%s",err.Error())
return
iferr!=nil{
logger.Errorf(ctx,"error:%+v",err)
簡化錯(cuò)誤處理
Golang因?yàn)榇a中無數(shù)的iferr!=nil被詬病,現(xiàn)在我們看看如何減少iferr!=nil這種代碼
bufio.scan
CountLines()實(shí)現(xiàn)了讀取內(nèi)容的行數(shù)功能
可以利用bufio.scan()簡化error的處理:
funcCountLines(rio.Reader)(int,error){
var(
br=bufio.NewReader(r)
linesint
errerror
for{
_,err:=br.ReadString('\n')
lines++
iferr!=nil{
break
iferr!=io.EOF{
return0,nilsadwawa
returnlines,nil
funcCountLinesGracefulErr(rio.Reader)(int,error){
sc:=bufio.NewScanner(r)
lines:=0
forsc.Scan(){
lines++
returnlines,sc.Err()
bufio.NewScanner()返回一個(gè)Scanner對象,結(jié)構(gòu)體內(nèi)部包含了error類型,調(diào)用Err()方法即可返回封裝好的error
Golang源代碼中蘊(yùn)含著大量的優(yōu)秀設(shè)計(jì)思想,我們在閱讀源碼時(shí)從中學(xué)習(xí),并在實(shí)踐中得以運(yùn)用
typeScannerstruct{
rio.Reader//Thereaderprovidedbytheclient.
splitSplitFunc//Thefunctiontosplitthetokens.
maxTokenSizeint//Maximumsizeofatoken;modifiedbytests.
token[]byte//Lasttokenreturnedbysplit.
buf[]byte//Bufferusedasargumenttosplit.
startint//Firstnon-processedbyteinbuf.
endint//Endofdatainbuf.
errerror//Stickyerror.
emptiesint//Countofsuccessiveemptytokens.
scanCalledbool//Scanhasbeencalled;bufferisinuse.
donebool//Scanhasfinished.
func(s*Scanner)Err()error{
ifs.err==io.EOF{
returnnil
returns.err
errWriter
WriteResponse()函數(shù)實(shí)現(xiàn)了構(gòu)建HttpResponse功能
利用上面學(xué)到的思路,我們可以自己實(shí)現(xiàn)一個(gè)errWriter對象,簡化對error的處理
typeHeaderstruct{
Key,Valuestring
typeStatusstruct{
Codeint
Reasonstring
funcWriteResponse(wio.Writer,stStatus,headers[]Header,bodyio.Reader)error{
_,err:=fmt.Fprintf(w,"HTTP/1.1%d%s\r\n",st.Code,st.Reason)
iferr!=nil{
returnerr
for_,h:=rangeheaders{
_,err:=fmt.Fprintf(w,"%s:%s\r\n",h.Key,h.Value)
iferr!=nil{
returnerr
if_,err:=fmt.Fprintf(w,"\r\n");err!=nil{
returnerr
_,err=io.Copy(w,body)
returnerr
typeerrWriterstruct{
io.Writer
errerror
func(e*errWriter)Write(buf[]byte)(nint,errerror){
ife.err!=nil{
return0,e.err
n,e.err=e.Writer.Write(buf)
returnn,nil
funcWriteResponseGracefulErr(w
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 鄉(xiāng)村勞務(wù)服務(wù)合同范例
- 行政管理的國際經(jīng)驗(yàn)與教訓(xùn)借鑒試題及答案
- 行政管理市政學(xué)備考要素試題及答案
- 行政管理中的心理健康促進(jìn)試題及答案
- 2025年自考行政管理心理學(xué)試題及答案
- 行政管理中的政府與市場關(guān)系試題及答案
- 行政管理服務(wù)質(zhì)量試題及答案
- 行政效率與服務(wù)創(chuàng)新的平衡試題及答案
- 2025年公文寫作的備考策略及試題及答案
- 社會(huì)責(zé)任感對企業(yè)形象的影響試題及答案
- 5萬噸鋼筋加工配送中心項(xiàng)目
- 鞋廠制革企業(yè)安全風(fēng)險(xiǎn)分級(jí)管控和隱患排查治理雙體系方案資料(2022-2023新標(biāo)準(zhǔn))
- 消防應(yīng)急預(yù)案流程圖
- 老年患者營養(yǎng)支持途徑及配方選擇課件
- 2022年最新小升初英語試卷(含答案)
- 二環(huán)庚二烯(2,5-降冰片二烯)的理化性質(zhì)及危險(xiǎn)特性表
- “轉(zhuǎn)觀念、勇?lián)?dāng)、強(qiáng)管理、創(chuàng)一流”對標(biāo)工作整改方案
- 模具試模通知單
- 全科醫(yī)師培訓(xùn)的全科門診主要內(nèi)容教學(xué)
- 蘇州納米所綜合考試要點(diǎn)
- 離子交換設(shè)備設(shè)計(jì)計(jì)算(有公式)
評(píng)論
0/150
提交評(píng)論