日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
關(guān)于Go程序錯(cuò)誤處理的一些建議

本文轉(zhuǎn)載自微信公眾號(hào)「網(wǎng)管叨bi叨」,作者網(wǎng)管 。轉(zhuǎn)載本文請(qǐng)聯(lián)系網(wǎng)管叨bi叨公眾號(hào)。

Go的錯(cuò)誤處理這塊是日常被大家吐槽較多的地方,我在工作中也觀察到一些現(xiàn)象,比較嚴(yán)重的是在各層級(jí)的邏輯代碼中對(duì)錯(cuò)誤的處理有些重復(fù)。

比如,有人寫代碼就會(huì)在每一層都判斷錯(cuò)誤并記錄日志,從代碼層面看,貌似很嚴(yán)謹(jǐn),但是如果看日志會(huì)發(fā)現(xiàn)一堆重復(fù)的信息,等到排查問題時(shí)反而會(huì)造成干擾。

今天給大家總結(jié)三點(diǎn)Go代碼錯(cuò)誤處理相關(guān)的最佳實(shí)踐給大家。

這些最佳實(shí)踐也是網(wǎng)上一些前輩分享的,我自己實(shí)踐后在這里用自己的語(yǔ)言描述出來,希望能對(duì)大家有所幫助。

認(rèn)識(shí)error

Go程序通過error類型的值表示錯(cuò)誤

error類型是一個(gè)內(nèi)建接口類型,該接口只規(guī)定了一個(gè)返回字符串值的Error方法。

 
 
 
  1. type error interface { 
  2.     Error() string 

Go語(yǔ)言的函數(shù)經(jīng)常會(huì)返回一個(gè)error值,調(diào)用者通過測(cè)試error值是否是nil來進(jìn)行錯(cuò)誤處理。

 
 
 
  1. i, err := strconv.Atoi("42") 
  2. if err != nil { 
  3.     fmt.Printf("couldn't convert number: %v\n", err) 
  4.     return 
  5. fmt.Println("Converted integer:", i) 

error為nil時(shí)表示成功;非nil的error表示失敗。

自定義錯(cuò)誤記得要實(shí)現(xiàn)error接口

我們經(jīng)常會(huì)定義符合自己需要的錯(cuò)誤類型,但是記住要讓這些類型實(shí)現(xiàn)error接口,這樣就不用在調(diào)用方的程序里引入額外的類型。

比如下面我們自己定義了myError這個(gè)類型,如果不實(shí)現(xiàn)error接口的話,調(diào)用者的代碼中就會(huì)被myError這個(gè)類型侵入。比如下面的run函數(shù),在定義返回值類型時(shí),直接定義成error即可。

 
 
 
  1. package myerror 
  2.  
  3. import ( 
  4.  "fmt" 
  5.  "time" 
  6.  
  7. type myError struct { 
  8.  Code int 
  9.  When time.Time 
  10.  What string 
  11.  
  12. func (e *myError) Error() string { 
  13.  return fmt.Sprintf("at %v, %s, code %d", 
  14.   e.When, e.What, e.Code) 
  15.  
  16. func run() error { 
  17.  return &MyError{ 
  18.     1002, 
  19.     time.Now(), 
  20.     "it didn't work", 
  21.  } 
  22.  
  23. func TryIt() { 
  24.  if err := run(); err != nil { 
  25.   fmt.Println(err) 
  26.  } 

如果myError不實(shí)現(xiàn)error接口的話,這里的返回值類型就要定義成myError類型??上攵o接著調(diào)用者的程序里就要通過myError.Code == xxx 來判斷到底是哪種具體的錯(cuò)誤(當(dāng)然想要這么干得先把myError改成導(dǎo)出的MyError)。

那調(diào)用者判斷自定義error是具體哪種錯(cuò)誤的時(shí)候應(yīng)該怎么辦呢,myError并未向包外暴露,答案是通過向包外暴露檢查錯(cuò)誤行為的方法來實(shí)現(xiàn)。

 
 
 
  1. myerror.IsXXXError(err)  
  2. ... 

抑或是通過比較error本身與包向外暴露的常量錯(cuò)誤是否相等來判斷,比如操作文件時(shí)常用來判斷文件是否結(jié)束的io.EOF。

類似的還有g(shù)orm.ErrRecordNotFound等各種開源包對(duì)外暴露的錯(cuò)誤常量。

 
 
 
  1. if err != io.EOF { 
  2.     return err 

錯(cuò)誤處理常犯的錯(cuò)誤

先看一段簡(jiǎn)單的程序,看大家能不能發(fā)現(xiàn)一些細(xì)微的問題

 
 
 
  1. func WriteAll(w io.Writer, buf []byte) error { 
  2.     _, err := w.Write(buf) 
  3.     if err != nil { 
  4.         log.Println("unable to write:", err) // annotated error goes to log file 
  5.         return err                           // unannotated error returned to caller 
  6.     } 
  7.     return nil 
  8.  
  9. func WriteConfig(w io.Writer, conf *Config) error { 
  10.     buf, err := json.Marshal(conf) 
  11.     if err != nil { 
  12.         log.Printf("could not marshal config: %v", err) 
  13.         return err 
  14.     } 
  15.     if err := WriteAll(w, buf); err != nil { 
  16.         log.Println("could not write config: %v", err) 
  17.         return err 
  18.     } 
  19.     return nil 
  20.  
  21. func main() { 
  22.     err := WriteConfig(f, &conf) 
  23.     fmt.Println(err) // io.EOF 

錯(cuò)誤處理常犯的兩個(gè)問題

上面程序的錯(cuò)誤處理暴露了兩個(gè)問題:

底層函數(shù)WriteAll在發(fā)生錯(cuò)誤后,除了向上層返回錯(cuò)誤外還向日志里記錄了錯(cuò)誤,上層調(diào)用者做了同樣的事情,記錄日志然后把錯(cuò)誤再返回給程序頂層。

因此在日志文件中得到一堆重復(fù)的內(nèi)容

 
 
 
  1. unable to write: io.EOF 
  2. could not write config: io.EOF 
  3. ... 

2. 在程序的頂部,雖然得到了原始錯(cuò)誤,但沒有相關(guān)內(nèi)容,換句話說沒有把WriteAll、WriteConfig記錄到 log 里的那些信息包裝到錯(cuò)誤里,返回給上層。

針對(duì)這兩個(gè)問題的解決方案可以是,在底層函數(shù)WriteAll、WriteConfig中為發(fā)生的錯(cuò)誤添加上下文信息,然后將錯(cuò)誤返回上層,由上層程序最后處理這些錯(cuò)誤。

一種簡(jiǎn)單的包裝錯(cuò)誤的方法是使用fmt.Errorf函數(shù),給錯(cuò)誤添加信息。

 
 
 
  1. func WriteConfig(w io.Writer, conf *Config) error { 
  2.     buf, err := json.Marshal(conf) 
  3.     if err != nil { 
  4.         return fmt.Errorf("could not marshal config: %v", err) 
  5.     } 
  6.     if err := WriteAll(w, buf); err != nil { 
  7.         return fmt.Errorf("could not write config: %v", err) 
  8.     } 
  9.     return nil 
  10. func WriteAll(w io.Writer, buf []byte) error { 
  11.     _, err := w.Write(buf) 
  12.     if err != nil { 
  13.         return fmt.Errorf("write failed: %v", err) 
  14.     } 
  15.     return nil 

給錯(cuò)誤附加上下文信息

fmt.Errorf只是給錯(cuò)誤添加了簡(jiǎn)單的注解信息,如果你想在添加信息的同時(shí)還加上錯(cuò)誤的調(diào)用棧,可以借助github.com/pkg/errors這個(gè)包提供的錯(cuò)誤包裝能力。

 
 
 
  1. //只附加新的信息 
  2. func WithMessage(err error, message string) error 
  3.  
  4. //只附加調(diào)用堆棧信息 
  5. func WithStack(err error) error 
  6.  
  7. //同時(shí)附加堆棧和信息 
  8. func Wrap(err error, message string) error 

有包裝方法,就有對(duì)應(yīng)的解包方法,Cause方法會(huì)返回包裝錯(cuò)誤對(duì)應(yīng)的最原始錯(cuò)誤--即會(huì)遞歸地進(jìn)行解包。

 
 
 
  1. func Cause(err error) error 

下面是使用github.com/pkg/errors改寫后的錯(cuò)誤處理程序

 
 
 
  1. func ReadFile(path string) ([]byte, error) { 
  2.     f, err := os.Open(path) 
  3.     if err != nil { 
  4.         return nil, errors.Wrap(err, "open failed") 
  5.     } 
  6.     defer f.Close() 
  7.     buf, err := ioutil.ReadAll(f) 
  8.     if err != nil { 
  9.         return nil, errors.Wrap(err, "read failed") 
  10.     } 
  11.     return buf, nil 
  12. func ReadConfig() ([]byte, error) { 
  13.     home := os.Getenv("HOME") 
  14.     config, err := ReadFile(filepath.Join(home, ".settings.xml")) 
  15.     return config, errors.WithMessage(err, "could not read config") 
  16.  
  17.  
  18. func main() { 
  19.     _, err := ReadConfig() 
  20.     if err != nil { 
  21.         fmt.Printf("original error: %T %v\n", errors.Cause(err), errors.Cause(err)) 
  22.         fmt.Printf("stack trace:\n%+v\n", err) 
  23.         os.Exit(1) 
  24.     } 

上面格式化字符串時(shí)用的 %+v 是在 % v 基礎(chǔ)上,對(duì)值進(jìn)行展開,即展開復(fù)合類型值,比如結(jié)構(gòu)體的字段值等明細(xì)。

這樣既能給錯(cuò)誤添加調(diào)用棧信息,又能保留對(duì)原始錯(cuò)誤的引用,通過Cause可以還原到最初始引發(fā)錯(cuò)誤的原因。

總結(jié)

總結(jié)一下,錯(cuò)誤處理的原則就是:

錯(cuò)誤只在邏輯的最外層處理一次,底層只返回錯(cuò)誤。

底層除了返回錯(cuò)誤外,要對(duì)原始錯(cuò)誤進(jìn)行包裝,增加錯(cuò)誤信息、調(diào)用棧等這些有利于排查的上下文信息。


當(dāng)前名稱:關(guān)于Go程序錯(cuò)誤處理的一些建議
鏈接分享:http://m.5511xx.com/article/dhcppci.html