成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

關于Go程序錯誤處理的一些建議

開發(fā) 后端
Go的錯誤處理這塊是日常被大家吐槽較多的地方,我在工作中也觀察到一些現象,比較嚴重的是在各層級的邏輯代碼中對錯誤的處理有些重復。

[[425992]]

本文轉載自微信公眾號「網管叨bi叨」,作者網管 。轉載本文請聯系網管叨bi叨公眾號。

Go的錯誤處理這塊是日常被大家吐槽較多的地方,我在工作中也觀察到一些現象,比較嚴重的是在各層級的邏輯代碼中對錯誤的處理有些重復。

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

今天給大家總結三點Go代碼錯誤處理相關的最佳實踐給大家。

這些最佳實踐也是網上一些前輩分享的,我自己實踐后在這里用自己的語言描述出來,希望能對大家有所幫助。

認識error

Go程序通過error類型的值表示錯誤

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

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

Go語言的函數經常會返回一個error值,調用者通過測試error值是否是nil來進行錯誤處理。

  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時表示成功;非nil的error表示失敗。

自定義錯誤記得要實現error接口

我們經常會定義符合自己需要的錯誤類型,但是記住要讓這些類型實現error接口,這樣就不用在調用方的程序里引入額外的類型。

比如下面我們自己定義了myError這個類型,如果不實現error接口的話,調用者的代碼中就會被myError這個類型侵入。比如下面的run函數,在定義返回值類型時,直接定義成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不實現error接口的話,這里的返回值類型就要定義成myError類型??上攵?,緊接著調用者的程序里就要通過myError.Code == xxx 來判斷到底是哪種具體的錯誤(當然想要這么干得先把myError改成導出的MyError)。

那調用者判斷自定義error是具體哪種錯誤的時候應該怎么辦呢,myError并未向包外暴露,答案是通過向包外暴露檢查錯誤行為的方法來實現。

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

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

類似的還有gorm.ErrRecordNotFound等各種開源包對外暴露的錯誤常量。

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

錯誤處理常犯的錯誤

先看一段簡單的程序,看大家能不能發(fā)現一些細微的問題

  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 

錯誤處理常犯的兩個問題

上面程序的錯誤處理暴露了兩個問題:

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

因此在日志文件中得到一堆重復的內容

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

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

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

一種簡單的包裝錯誤的方法是使用fmt.Errorf函數,給錯誤添加信息。

  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 

給錯誤附加上下文信息

fmt.Errorf只是給錯誤添加了簡單的注解信息,如果你想在添加信息的同時還加上錯誤的調用棧,可以借助github.com/pkg/errors這個包提供的錯誤包裝能力。

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

有包裝方法,就有對應的解包方法,Cause方法會返回包裝錯誤對應的最原始錯誤--即會遞歸地進行解包。

  1. func Cause(err error) error 

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

  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.     } 

上面格式化字符串時用的 %+v 是在 % v 基礎上,對值進行展開,即展開復合類型值,比如結構體的字段值等明細。

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

總結

總結一下,錯誤處理的原則就是:

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

 

底層除了返回錯誤外,要對原始錯誤進行包裝,增加錯誤信息、調用棧等這些有利于排查的上下文信息。

 

責任編輯:武曉燕 來源: 網管叨bi叨
相關推薦

2021-09-27 15:33:48

Go 開發(fā)技術

2020-08-20 10:16:56

Golang錯誤處理數據

2014-11-17 10:05:12

Go語言

2021-04-29 09:02:44

語言Go 處理

2015-08-26 09:31:26

程序員建議

2015-08-26 08:31:35

核心程序員成長

2011-04-27 09:21:09

程序員

2025-06-06 06:45:54

2021-09-13 07:53:31

Go錯誤處理

2025-03-31 00:29:44

2022-09-05 08:55:15

Go2提案語法

2025-06-30 09:49:11

2011-07-13 09:13:56

Android設計

2019-10-30 14:58:45

MVCAndroid表現層

2009-11-26 15:10:08

VS2005資源

2022-12-12 08:53:53

Go版本方式

2023-10-26 15:49:53

Go日志

2020-12-17 06:25:05

Gopanic 模式

2021-09-27 23:28:29

Go多協程并發(fā)

2011-07-27 10:52:02

Oracleredolog
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产一级片在线观看视频 | 视频一区二区国产 | 伊人精品在线视频 | 天天躁日日躁狠狠躁2018小说 | 欧美日韩理论 | 久久伊人亚洲 | 99爱国产 | 一区二区三区四区在线播放 | 日韩免费在线视频 | av毛片| 超碰在线97国产 | 狠狠色综合久久丁香婷婷 | 日韩成人专区 | 国产在线观看网站 | 国产成人精品综合 | 一级黄色淫片 | 国产成人精品一区二区三区四区 | 在线播放国产一区二区三区 | 日日操操 | 国产精品成人在线播放 | 夜夜精品浪潮av一区二区三区 | 中国一级特黄真人毛片 | 一区二区三区欧美 | 天堂久| 中文字幕啪啪 | 国产欧美一区二区三区日本久久久 | 欧美黑人一级爽快片淫片高清 | 国产精品免费小视频 | 成年人免费看 | 欧美精品久久久久 | 久久精品手机视频 | 伊人狠狠 | 日韩伦理一区二区 | 国产一区免费 | 亚洲国产成人在线观看 | 日韩高清成人 | 午夜精品久久久久久久久久久久久 | 成人免费观看视频 | 国产精品99久久久久久久vr | 久久精品国产久精国产 | 97伦理最新伦理 |