您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何處理關于Go程序錯誤”,在日常操作中,相信很多人在如何處理關于Go程序錯誤問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何處理關于Go程序錯誤”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
前言
認識error
自定義錯誤記得要實現error接口
錯誤處理常犯的錯誤
錯誤處理常犯的兩個問題
給錯誤附加上下文信息
總結
Go的錯誤處理這塊是日常被大家吐槽較多的地方,我在工作中也觀察到一些現象,比較嚴重的是在各層級的邏輯代碼中對錯誤的處理有些重復。
比如,有人寫代碼就會在每一層都判斷錯誤并記錄日志,從代碼層面看,貌似很嚴謹,但是如果看日志會發現一堆重復的信息,等到排查問題時反而會造成干擾。
今天給大家總結三點Go代碼錯誤處理相關的最佳實踐給大家。
這些最佳實踐也是網上一些前輩分享的,我自己實踐后在這里用自己的語言描述出來,希望能對大家有所幫助。
Go程序通過error類型的值表示錯誤
error類型是一個內建接口類型,該接口只規定了一個返回字符串值的Error方法。
type error interface { Error() string }
Go語言的函數經常會返回一個error值,調用者通過測試error值是否是nil來進行錯誤處理。
i, err := strconv.Atoi("42") if err != nil { fmt.Printf("couldn't convert number: %v\n", err) return } fmt.Println("Converted integer:", i)
error為nil時表示成功;非nil的error表示失敗。
我們經常會定義符合自己需要的錯誤類型,但是記住要讓這些類型實現error接口,這樣就不用在調用方的程序里引入額外的類型。
比如下面我們自己定義了myError這個類型,如果不實現error接口的話,調用者的代碼中就會被myError這個類型侵入。比如下面的run函數,在定義返回值類型時,直接定義成error即可。
package myerror import ( "fmt" "time" ) type myError struct { Code int When time.Time What string } func (e *myError) Error() string { return fmt.Sprintf("at %v, %s, code %d", e.When, e.What, e.Code) } func run() error { return &MyError{ 1002, time.Now(), "it didn't work", } } func TryIt() { if err := run(); err != nil { fmt.Println(err) } }
如果myError不實現error接口的話,這里的返回值類型就要定義成myError類型。可想而知,緊接著調用者的程序里就要通過myError.Code == xxx 來判斷到底是哪種具體的錯誤(當然想要這么干得先把myError改成導出的MyError)。
那調用者判斷自定義error是具體哪種錯誤的時候應該怎么辦呢,myError并未向包外暴露,答案是通過向包外暴露檢查錯誤行為的方法來實現。
myerror.IsXXXError(err) ...
抑或是通過比較error本身與包向外暴露的常量錯誤是否相等來判斷,比如操作文件時常用來判斷文件是否結束的io.EOF。
類似的還有gorm.ErrRecordNotFound等各種開源包對外暴露的錯誤常量。
if err != io.EOF { return err }
先看一段簡單的程序,看大家能不能發現一些細微的問題
func WriteAll(w io.Writer, buf []byte) error { _, err := w.Write(buf) if err != nil { log.Println("unable to write:", err) // annotated error goes to log file return err // unannotated error returned to caller } return nil } func WriteConfig(w io.Writer, conf *Config) error { buf, err := json.Marshal(conf) if err != nil { log.Printf("could not marshal config: %v", err) return err } if err := WriteAll(w, buf); err != nil { log.Println("could not write config: %v", err) return err } return nil } func main() { err := WriteConfig(f, &conf) fmt.Println(err) // io.EOF }
上面程序的錯誤處理暴露了兩個問題:
1.底層函數WriteAll在發生錯誤后,除了向上層返回錯誤外還向日志里記錄了錯誤,上層調用者做了同樣的事情,記錄日志然后把錯誤再返回給程序頂層。
因此在日志文件中得到一堆重復的內容
unable to write: io.EOF
could not write config: io.EOF
...
2. 在程序的頂部,雖然得到了原始錯誤,但沒有相關內容,換句話說沒有把WriteAll、WriteConfig記錄到 log 里的那些信息包裝到錯誤里,返回給上層。
針對這兩個問題的解決方案可以是,在底層函數WriteAll、WriteConfig中為發生的錯誤添加上下文信息,然后將錯誤返回上層,由上層程序最后處理這些錯誤。
一種簡單的包裝錯誤的方法是使用fmt.Errorf函數,給錯誤添加信息。
func WriteConfig(w io.Writer, conf *Config) error { buf, err := json.Marshal(conf) if err != nil { return fmt.Errorf("could not marshal config: %v", err) } if err := WriteAll(w, buf); err != nil { return fmt.Errorf("could not write config: %v", err) } return nil } func WriteAll(w io.Writer, buf []byte) error { _, err := w.Write(buf) if err != nil { return fmt.Errorf("write failed: %v", err) } return nil }
fmt.Errorf只是給錯誤添加了簡單的注解信息,如果你想在添加信息的同時還加上錯誤的調用棧,可以借助github.com/pkg/errors這個包提供的錯誤包裝能力。
//只附加新的信息 func WithMessage(err error, message string) error //只附加調用堆棧信息 func WithStack(err error) error //同時附加堆棧和信息 func Wrap(err error, message string) error
有包裝方法,就有對應的解包方法,Cause方法會返回包裝錯誤對應的最原始錯誤--即會遞歸地進行解包。
func Cause(err error) error
下面是使用github.com/pkg/errors改寫后的錯誤處理程序
func ReadFile(path string) ([]byte, error) { f, err := os.Open(path) if err != nil { return nil, errors.Wrap(err, "open failed") } defer f.Close() buf, err := ioutil.ReadAll(f) if err != nil { return nil, errors.Wrap(err, "read failed") } return buf, nil } func ReadConfig() ([]byte, error) { home := os.Getenv("HOME") config, err := ReadFile(filepath.Join(home, ".settings.xml")) return config, errors.WithMessage(err, "could not read config") } func main() { _, err := ReadConfig() if err != nil { fmt.Printf("original error: %T %v\n", errors.Cause(err), errors.Cause(err)) fmt.Printf("stack trace:\n%+v\n", err) os.Exit(1) } }
上面格式化字符串時用的 %+v 是在 % v 基礎上,對值進行展開,即展開復合類型值,比如結構體的字段值等明細。
這樣既能給錯誤添加調用棧信息,又能保留對原始錯誤的引用,通過Cause可以還原到最初始引發錯誤的原因。
到此,關于“如何處理關于Go程序錯誤”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。