丨錯誤處理下如何設計包_第1頁
丨錯誤處理下如何設計包_第2頁
丨錯誤處理下如何設計包_第3頁
丨錯誤處理下如何設計包_第4頁
丨錯誤處理下如何設計包_第5頁
已閱讀5頁,還剩17頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

代123456789package代123456789package("fmtfuncmain()iferr:=funcA();err!={log.Fatalf("callfuncgotfailed:%v",err)return}log.Println("callfunc}funcfuncA()erroriferr:=funcB();err!={return}returnfmt.Errorf("func}funcfuncB()errorreturnfmt.Errorf("func}執行上面的代碼$gorun2021/07/0208:06:55callfuncgotfailed:funccalledexitstatus這時我們想定位問題,但不知體是哪行代碼報的錯誤,只能靠猜,還不一定能猜到。為了解決這個問題,我們可以加一些Debug信息,來協助我們定位問題。這樣做在測試環境是沒問題的,但是上環境,一方面修改、發布都比較麻煩,另一方面問題可能比較難重現。這時候我們會想,要是能打印錯誤的堆棧就好了。例如:1234567892021/07/0214:17:03callfuncgotfailed:funccalledexitstatus通過上面的錯誤輸出,我們可以很容易地知道是哪行代碼報的錯,從而極大提高問題定位的效率,降低定位的難度。所以,在我看來,一個優秀的errors包,首先需要支持錯誤堆棧。其次,能夠支持不同的打印格式。例如%+v、%v、s等格式,可以根據需要打印不同豐富度的錯誤信息。再次,能支持Wrap/Unwrap功能,也就是在已有的錯誤上,追加一些新的信息。例如errors.Wrap(err"openfilefailed"。Wrap常用在調用函數中,調用函數可以基于被調函數報錯時的錯誤Wrap一些自己的信息,豐富報錯信息,方便后期的錯誤代代12345678funcfuncA()erroriferr:=funcB();err!=nilreturnerrors.Wrap(err,"callfuncB}returnerrors.New("funccalled}funcfuncfuncB()errorreturnerrors.New("funccalled11這里要注意,不同的錯誤類型,Wrap函數的邏輯也可以不同。另外,在調用Wrap時,也會生成一個錯誤堆棧節點。然能夠嵌套error,那有時候還可能需要獲取被嵌套的還有,錯誤包應該有Is方法。在實際開發中,我們經常需要判斷某個error是否是指定的error。在Go1.13之前,也就是沒有wraperror的時候,我們要判斷error是不iferr==os.ErrNotExist//normal3但是現在,因為有了wraperror,這樣判斷就會有問題。因為你根本不知道返回的err是不是一個嵌套的error,嵌套了幾層。這種情況下,我們的錯誤包就需要提供Is函1funcIs(err,targeterror)當err和target是同一個,或者err是一個wraperror的時候,如果target也包在這個嵌套error鏈中,返回true,否則返回fasle。另外,錯誤包應該支持As函數在Go1.13之前,沒有wraperror的時候,我們要把error轉為另外一個error,般都是使用typeassertion或者typeswitch,也就是類型斷言。例如:ifperr,ok:=err.(*os.PathError);ok33但是現在,返回的err可能是嵌套的error,甚至好幾層嵌套,這種方式就不能用了。所以,我們可以通過實現As函數來完成這種功能。現在我們把上面的例子,用As函數實現varperriferrors.As(err,&perr)4這樣就可以完全實現類型斷言的功能,而且還更強大,因為它可以處理wraperror最后,能夠支持兩種錯誤創建方式:非格式化創建和格式化創建。例如errors.New("filenoterrors.Errorf("file%snotfound","iam- /pkg/errors包進行了二次封裝,用來支持上一講所介紹的錯誤明確優秀的錯誤包應該具備的功能后,我們來看下錯誤包的實現。實現的源碼存/marmotedu/errors /pkg/errors/errors.go中增加新的withCode結構體,來引 和具體的錯誤信息。typewithCodestruct error//errorcodeint//456causeerror//cause*stack//錯誤堆}456causeerror//cause*stack//錯誤堆}1package2import5 code 89funcmain()iferr:=bindUser();err!=nil//%s:Returnstheuser-safeerrorstringmappedtotheerrorcodeorfmt.Println("====================>%sfmt.Printf("%s\n\n",//%v:Aliasforfmt.Println("====================>%vfmt.Printf("%v\n\n",//%-v:Outputcallerdetails,usefulforfmt.Println("====================>%-vfmt.Printf("%-v\n\n",//%+v:Outputfullerrorstackdetails,usefulforfmt.Println("====================>%+vfmt.Printf("%+v\n\n",//%#-v:Outputcallerdetails,usefulfortroubleshootingwithJSONfmt.Println("====================>%#-vfmt.Printf("%#-v\n\n",//%#+v:Outputfullerrorstackdetails,usefulfordebuggingwithJSONfmt.Println("====================>%#+vfmt.Printf("%#+v\n\n",//dosomebusinessprocessbasedontheerroriferrors.IsCode(err,code.ErrEncodingFailed)fmt.Println("thisisaErrEncodingFailed {fmt.Println("thisisa}//wecanalsofindthecause}48funcbindUser()erroriferr:=getUser();err!=nil//Step3:Wraptheerrorwithanewerrormessageandanewerrorcodereturnerrors.WrapC(err,code.ErrEncodingFailed,"encodinguser'Lingfei}return}funcgetUser()erroriferr:=queryDatabase();err!=nil//Step2:Wraptheerrorwithanewerrorreturnerrors.Wrap(err,"getuser}return}funcqueryDatabase()error//Step1.Createerrorwithspecifiederrorreturnerrors.WithCode(code.ErrDatabase,"user'LingfeiKong'not}iferrors.IsCode(err,上述代碼中,通過WithCode函數來創建新的withCode類型的錯誤;通過來將一個error封裝成一個withCode類型的錯誤;通過 IsCode來判斷一個erroriferrors.IsCode(err,withCode誤實現了一個funcw*withCodeFormat(statefmt.State,verbrune)方法,該方法用來打印不同格式的錯誤信息,見下表:例如,%+v會打印以下錯誤信11getuserfailed.-#1那么你可能會問,這些錯誤信息中的100101錯誤碼,還有Databaseerror這種對外展首先withCode中包含了int類型的錯誤碼,例如100101 MustRegister,將一個Coder到 1varcodes=代typeCoderinterface//HTTPstatusthatshould代typeCoderinterface//HTTPstatusthatshouldbeusedfortheassociatederrorHTTPStatus()45//External(user)facing67String()8//Referencereturnsthesfor9Reference()//CodereturnsthecodeofCode()13這樣withCode的Format方法,就能夠通過withCode中的code字段獲取到對應的Coder,并通過Coder提供的HTTPStatus、String、Reference、Code函數,來獲取withCode中code的詳細信息,最后格式化打印。這里要注意,我們實現了兩個函數:Register和MustRegister,二者唯一區是:當重復定義同一個錯誤Code,MustRegisterpanic,這樣可以防止后面XXX()和MustXXX()的函數命名方式,是一種Go代碼設計技巧,在Go代碼中經常使用,例如Go標準庫中 MustXXX會在某種情況不滿足時panic。因此使用MustXXX的開發者看到函數名就會有一個心理預期:使用不當,會造成程序panic。最后,我還有一個建議:在實際的生產環境中,我們可以使用JSON格式打印日志,JSON格式的日志可以非常方便的供日志系統解析。我們可以根據需要,選擇%#-v或%#+v兩種格 在這里,我們把這個錯誤包跟go標準庫的errors包,以/pkg/errors包進行對比,來看看它們的性 1547 734 2881 3116 7159 10646 11896 持平。在對比性能時,重點關注ns/op,也即每次error操作耗費的納秒數。另外,我們還需要測試不同error嵌套深度下的error操作性能,嵌套越深,性能越差。例如:在嵌套深度為10的時候, /pkg/errors包ns/op值為1547,/marmotedu/errors包ns/op值為1772。可以看到,二者性能基本保一致具體性能數據對比見下表我們是通過Ben arkErrors測試函數來測試error包性能的,你感可以打開鏈根據我的開發經驗,我推薦兩種記錄錯誤的方式,可以幫你快速定位問 /marmotedu/errors包提供的錯誤堆棧能力,來錯誤。具體你下面的代碼示例。以下代碼保存在 代代123456package("7789code)funcmain()iferr:=getUser();err!={fmt.Printf("%+v\n",}}funcgetUser()erroriferr:=queryDatabase();err!={returnerrors.Wrap(err,"getuserreturn}funcqueryDatabase()errorreturn}"user'LingfeiKong'not執行上述的代碼$gorungetuserfailed.-#1你使用這種方法時,我推薦的用法是,在錯誤最開始處使用errors.WithCode(創建一個withCode類型的錯誤。上層在處理底層返回的錯誤時,可以根據需要,使用Wrap函數基于該錯誤封裝新的錯誤信息。如果要包裝的error不是用/marmotedu/errors包創建的,建議用errors.WithCode()新建error1package23import456"7"89code )funcmain()iferr:=getUser();err!=nilfmt.Printf("%v\n",}}funcgetUser()erroriferr:=queryDatabase();err!=nilreturn}return}funcqueryDatabase()erroropts:= []string{"test.log",ErrorOutputPaths:}defererr:=errors.WithCode(code.ErrDatabase,"user'LingfeiKong'notiferr!=nillog.Errorf("%v",}return}執行以上代碼1123$gorun代2021-07-03DatabaseDatabase當錯誤發生時,調用log打印錯誤。通過log的caller能,可以定位到log句的當代碼調用第包的函數時,第包函數出錯時打印錯誤信息。比如iferr:=os.Chdir("/root");err!=nillog.Errorf("changedirfailed:%v",3接下來,我們看一個依據上一講介紹的錯誤碼規范的具體錯誤碼/marmotedu/sample-codesample-code實現了兩類錯誤碼,分別是通用錯誤碼(sample-code/base.go)和業務模塊相關的錯誤碼(sample-code/apiserver.go)。首先,我們來看通用錯誤碼的定代代123456789//通用:基本錯//Codemuststartwith1xxxxxconst(//ErrSuccess-200:ErrSuccessint=iota+//ErrUnknown-500:Internalservererror.//ErrBind-400:Erroroccurredwhilebindingtherequestbodytothestr//ErrValidation-400:Validationfailed.//ErrTokenInvalid-401:Tokeninvalid.)在代碼中,我們通常使用整型常量(ErrSucess)來代替整型錯誤碼(100001),因為使用ErrScess時,一看就知道它代表的錯誤類型,可以方便開發者使用。錯誤碼用來指代一個錯誤類型,該錯誤類型需要包含一些有用的信息,例如對應的HTTPStatusCode、對外展示的Message,以及跟該錯誤匹配的幫助文檔。所以,我們還需要實現一個Coder來承載這些信息。這里,我們定義了一個實現了/marmotedu/errors.Coder接口的ErrCode結構代代123456789//ErrCodeimplementstypeErrCodestruct/marmotedu/errors`.Coder//CreferstothecodeoftheC//HTTPstatusthatshouldbeusedfortheassociatederrorHTTP//External(user)facingerrortext.Extstring//RefspecifythereferenceRefstring.}可以看到ErrCode結構體包含了以下信int類型的業務碼對應的HTTPStatusCode。錯誤的參考文檔下面是一個具體的Coder示例23456"1coder:= MustRegister函數,將Coder到 1coder:=一個項目有很多個錯誤碼,如果每個錯誤碼都手動調用Mustegister函數會很麻煩,這里我們通過代碼自動生成的方法,來生成reister函數調用://go:generatecodegen-//go:generatecodegen-type=int-doc-output//go:generatecodegen-type=int會調用codegen工具,生成funcinit()register(ErrSuccess,200,register(ErrUnknown,500,"Internalserverregister(ErrBind,400,"Erroroccurredwhilebindingtherequestbodytoregister(ErrValidation,400,"Validation//otherregisterfunction7這些register調用放 函數中,在加載程序的時候被初始化這里要注意,在的時候,我們會檢查HTTPStatusCode,只允許定義200、400、401、403、404、500這6個HTTP錯誤碼。這里通過程序保證了錯誤碼是符合HTTPStatusCode使用要求的。//go:generatecodegen-type=int-doc- 我們提供API文檔時,也需要記著提供一份錯誤碼描述文檔,這樣客戶端才可以根據錯誤//ErrSuccess-200:ErrSuccessint=iota+codegen工具之所以能夠生成sample_code_generated.go和error_code_generated.md,是因為我們的錯誤碼注釋是有規定格式的:錯誤碼整型常量>-<對應的HTTPStatusCode>:<ExternalMessage>.。codegen工具可以在IAM項目 下,執行以下命令來安裝1$make安裝完codegen工具后,可以在 /marmotedu/sample-code包根 執行gogenerate命令,來生成sample_code_generated.go和xxxx_generated.xx來命名,這樣通過generated,我們就知道這個文件是代碼自動在實際的開發中,我們可以將錯誤碼獨立成一個包,放在下,這樣可以方便整個應用調用。例如IAM的錯誤碼就放在IAM項目 下 我們的錯誤碼是分服務和模塊的,所以這里建議你把相同的服務放在同一個Go文件中,例如IAM的錯誤碼存放文件:12$lsbase.goapiserver.go一個應用中會有多個服務,例如IAM應用中,就包含了iam-apiserver、iam-authz-server、iam-pump三個服務。這些服務有一些通用的錯誤碼,為了便于,可以將這些通用的錯誤碼統一放在base.go源碼文件中。其他的錯誤碼,我們可以按服務分別放在不同的文件中:iam-apiserver服務的錯誤碼統一放在apiserver.go文件中;iam-authz-server的錯誤碼統一存放在authzserver.go文件中。其他服務以此類推。另外,同一個服務中不同模塊的錯誤碼,可以按以下格式來組織:相同模塊的錯誤碼放在同一個cost代碼塊中,不同模塊的錯誤碼放在不同的onst代碼塊中。每個onst塊的開頭注釋就是該模塊的錯誤碼定義。例如:代代123456789//iam-apiserver:usererrors.const(//ErrUserNotFound-404:UsernotErrUserNotFoundint=iota+//ErrUserAlreadyExist-400:Useralready)//iam-apiserver:secreterrors.const(//ErrEncrypt-400:SecretreachthemaxaxCountint=iota+ErrSecretNotFound-404:Secretnot)IAM項目的錯誤碼定義記錄文檔為code_specification.md。這個文檔中記錄了錯誤碼用的。這里,我就舉一個在ginweb框架中使用該錯誤碼的例子://ResponsedefinesprojectresponseformatwhichinmarmotedutypeResponsestruct errors.Code Reference interface{}78//WriteResponseusedtowriteanerrorandJSONdataintofuncWriteResponse(c*gin.Context,errerror,datainterface{})err!=nilcoder:=c.JSON(coder.HTTPStatus(), Reference: }23

c.JSON(http.StatusOK,Response{Da

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論