自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

先睹為快,Go2 Error 的掙扎之路

開發(fā) 后端
自從 Go 語言在國內(nèi)火熱以來,除去泛型,其次最具槽點的就是 Go 對錯誤的處理方式,一句經(jīng)典的 if err != nil 暗號就能認出你是一個 Go 語言愛好者。

 [[356198]]

本文轉(zhuǎn)載自微信公眾號「腦子進煎魚了」,作者陳煎魚 。轉(zhuǎn)載本文請聯(lián)系腦子進煎魚了公眾號。   

大家好,我是煎魚。

自從 Go 語言在國內(nèi)火熱以來,除去泛型,其次最具槽點的就是 Go 對錯誤的處理方式,一句經(jīng)典的 if err != nil 暗號就能認出你是一個 Go 語言愛好者。

自然,大家對 Go error 的關注度更是高漲,Go team 也是,因此在 Go 2 Draft Designs 中正式提到了 error handling(錯誤處理)的相關草案,希望能夠在未來正式的解決這個問題。

在今天這篇文章中,我們將一同跟蹤 Go2 error,看看他是怎么 “掙扎” 的,能不能破局?

為什么要吐槽 Go1

要吐槽 Go1 error,就得先知道為什么大家到底是在噴 Error 哪里處理的不好。在 Go 語言中,error 其實本質(zhì)上只是個 Error 的 interface:

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

實際的應用場景如下:

  1. func main() { 
  2.  x, err := foo() 
  3.  if err != nil { 
  4.    // handle error 
  5.  } 

單純的看這個例子似乎沒什么問題,但工程大了后呢?

顯然 if err != nil 的邏輯是會堆積在工程代碼中,Go 代碼里的 if err != nil 甚至會達到工程代碼量的 30% 以上:

  1. func main() { 
  2.  x, err := foo() 
  3.  if err != nil { 
  4.    // handle error 
  5.  } 
  6.  y, err := foo() 
  7.  if err != nil { 
  8.    // handle error 
  9.  } 
  10.  z, err := foo() 
  11.  if err != nil { 
  12.    // handle error 
  13.  } 
  14.  s, err := foo() 
  15.  if err != nil { 
  16.    // handle error 
  17.  } 

暴力的對比一下,就發(fā)現(xiàn)四行函數(shù)調(diào)用,十二行錯誤,還要苦練且精通 IDE 的快速折疊功能,還是比較麻煩的。

另外既然是錯誤處理,那肯定不單單是一個 return err 了。在工程實踐中,項目代碼都是層層嵌套的,如果直接寫成:

  1. if err != nil { 
  2.  return err 

在實際工程中肯定是不行。你怎么知道具體是哪里拋出來的錯誤信息,實際出錯時只能瞎猜。大家又想出了 PlanB,那就是加各種描述信息:

  1. if err != nil { 
  2.  logger.Errorf("煎魚報錯 err:%v", err) 
  3.  return err 

雖然看上去人模人樣的,在實際出錯時,也會遇到新的問題,因為你要去查這個錯誤是從哪里拋出來的,沒有調(diào)用堆棧,單純幾句錯誤描述是難以定位的。

這時候就會發(fā)展成到處打錯誤日志:

  1. func main() { 
  2.  err := bar() 
  3.  if err != nil { 
  4.   logger.Errorf("bar err:%v", err) 
  5.  } 
  6.  ... 
  7.  
  8. func bar() error { 
  9.  _, err := foo() 
  10.  if err != nil { 
  11.   logger.Errorf("foo err:%v", err) 
  12.   return err 
  13.  } 
  14.  
  15.  return nil 
  16.  
  17. func foo() ([]byte, error) { 
  18.  s, err := json.Marshal("hello world."
  19.  if err != nil { 
  20.   logger.Errorf("json.Marshal err:%v", err) 
  21.   return nil, err 
  22.  } 
  23.  
  24.  return s, nil 

雖然到處打了日志,就會變成錯誤日志非常多,一旦出問題,人肉可能短時間內(nèi)識別不出來。

最常見的就是到 IDE 上 ctrl + f 搜索是在哪出錯。同時在實際應用中我們會自定義一些錯誤類型,在 Go 則需要各種判斷和處理:

  1. if err := dec.Decode(&val); err != nil { 
  2.     if serr, ok := err.(*json.SyntaxError); ok { 
  3.        ... 
  4.     } 
  5.     return err 

你得判斷不等于 nil,還得對自定義的錯誤類型進行斷言,整體來講比較繁瑣。

匯總來講,Go1 錯誤處理的問題至少有:

  • 在工程實踐中,if err != nil 寫的煩,代碼中一大堆錯誤處理的判斷,占了相當?shù)谋壤?,不夠?yōu)雅。
  • 在排查問題時,Go 的 err 并沒有其他堆棧信息,只能自己增加描述信息,層層疊加,打一大堆日志,排查很麻煩。
  • 在驗證和測試錯誤時,要自定義錯誤(各種判斷和斷言)或者被迫用字符串校驗。

Go1.13 的挽尊

在 2019 年 09 月,Go1.13 正式發(fā)布。其中兩個比較大的兩個關注點分別是包依賴管理 Go modules 的轉(zhuǎn)正,以及錯誤處理 errors 標準庫的改進:

Error wrapping

在本次改進中,errors 標準庫引入了 Wrapping Error 的概念,并增加了 Is/As/Unwarp 三個方法,用于對所返回的錯誤進行二次處理和識別。

同時也是將 Go2 error 預規(guī)劃中沒有破壞 Go1 兼容性的相關功能提前實現(xiàn)了。

簡單來講,Go1.13 后 Go 的 error 就可以嵌套了,并提供了三個配套的方法。例子:

  1. func main() { 
  2.  e := errors.New("腦子進煎魚了"
  3.  w := fmt.Errorf("快抓?。?w", e) 
  4.  fmt.Println(w) 
  5.  fmt.Println(errors.Unwrap(w)) 

輸出結果:

  1. $ go run main.go 
  2. 快抓?。耗X子進煎魚了 
  3. 腦子進煎魚了 

在上述代碼中,變量 w 就是一個嵌套一層的 error。最外層是 “快抓?。?rdquo;,此處調(diào)用 %w 意味著 Wrapping Error 的嵌套生成。因此最終輸出了 “快抓?。耗X子進煎魚了”。

需要注意的是,Go 并沒有提供 Warp 方法,而是直接擴展了 fmt.Errorf 方法。而下方的輸出由于直接調(diào)用了 errors.Unwarp 方法,因此將 “取” 出一層嵌套,最終直接輸出 “腦子進煎魚了”。

對 Wrapping Error 有了基本理解后,我們簡單介紹一下三個配套方法:

  1. func Is(err, target error) bool 
  2. func As(err error, target interface{}) bool 
  3. func Unwrap(err error) error 

errors.Is

方法簽名:

  1. func Is(err, target error) bool 

方法例子:

  1. func main() { 
  2.  if _, err := os.Open("non-existing"); err != nil { 
  3.   if errors.Is(err, os.ErrNotExist) { 
  4.    fmt.Println("file does not exist"
  5.   } else { 
  6.    fmt.Println(err) 
  7.   } 
  8.  } 
  9.  

errors.Is 方法的作用是判斷所傳入的 err 和 target 是否同一類型,如果是則返回 true。

errors.As

方法簽名:

  1. func As(err error, target interface{}) bool 

方法例子:

  1. func main() { 
  2.  if _, err := os.Open("non-existing"); err != nil { 
  3.   var pathError *os.PathError 
  4.   if errors.As(err, &pathError) { 
  5.    fmt.Println("Failed at path:", pathError.Path) 
  6.   } else { 
  7.    fmt.Println(err) 
  8.   } 
  9.  } 
  10.  

errors.As 方法的作用是從 err 錯誤鏈中識別和 target 相同的類型,如果可以賦值,則返回 true。

errors.Unwarp

方法簽名:

  1. func Unwrap(err error) error 

方法例子:

  1. func main() { 
  2.  e := errors.New("腦子進煎魚了"
  3.  w := fmt.Errorf("快抓?。?w", e) 
  4.  fmt.Println(w) 
  5.  fmt.Println(errors.Unwrap(w)) 

該方法的作用是將嵌套的 error 解析出來,若存在多級嵌套則需要調(diào)用多次 Unwarp 方法。

民間自救 pkg/errors

Go1 的 error 處理固然存在許多問題,因此在 Go1.13 前,早已有 “民間” 發(fā)現(xiàn)沒有上下文調(diào)試信息在實際工程應用中存在嚴重的體感問題。

因此 github.com/pkg/errors 在 2016 年誕生了,該庫也已經(jīng)受到了極大的關注。

官方例子如下:

  1. type stackTracer interface { 
  2.     StackTrace() errors.StackTrace 
  3.  
  4. err, ok := errors.Cause(fn()).(stackTracer) 
  5. if !ok { 
  6.     panic("oops, err does not implement stackTracer"
  7.  
  8. st := err.StackTrace() 
  9. fmt.Printf("%+v", st[0:2]) // top two frames 
  10.  
  11. // Example output
  12. // github.com/pkg/errors_test.fn 
  13. // /home/dfc/src/github.com/pkg/errors/example_test.go:47 
  14. // github.com/pkg/errors_test.Example_stackTrace 
  15. // /home/dfc/src/github.com/pkg/errors/example_test.go:127 

簡單來講,就是對 Go1 error 的上下文處理進行了優(yōu)化和處理,例如類型斷言、調(diào)用堆棧等。若有興趣的小伙伴可以自行到 github.com/pkg/errors 進行學習。

另外你可能會發(fā)現(xiàn) Go1.13 新增的 Wrapping Error 體系與 pkg/errors 有些相像。

你并沒有體會錯,Go team 接納了相關的意見,對 Go1 進行了調(diào)整,但調(diào)用堆棧這塊因綜合原因暫時沒有納入。

Go2 error 要解決什么問題

在前面我們聊了 Go1 error 的許多問題,以及 Go1.13 和 pkg/errors 的自救和融合。你可能會疑惑,那...Go2 error 還有出場的機會嗎?即使 Go1 做了這些事情,Go1 error 還有問題嗎?

并沒有解決,if err != nil 依舊一把梭,目前社區(qū)聲音依然認為 Go 語言的錯誤處理要改進。

Go2 error proposal

在 2018 年 8 月,官方正式公布了 Go 2 Draft Designs,其中包含泛型和錯誤處理機制改進的初步草案:

Go2 Draft Designs

注:Go1.13 正式將一些不破壞 Go1 兼容性的 Error 特性加入到了 main branch,也就是前面提到的 Wrapping Error。

錯誤處理(Error Handling)

第一個要解決的問題就是大量 if err != nil 的問題,針對此提出了 Go2 error handling 的草案設計。

簡單例子:

  1. if err != nil { 
  2.  return err 

優(yōu)化后的方案如下:

  1. func CopyFile(src, dst string) error { 
  2.  handle err { 
  3.   return fmt.Errorf("copy %s %s: %v", src, dst, err) 
  4.  } 
  5.  
  6.  r := check os.Open(src) 
  7.  defer r.Close() 
  8.  
  9.  w := check os.Create(dst) 
  10.  handle err { 
  11.   w.Close() 
  12.   os.Remove(dst) // (only if a check fails) 
  13.  } 
  14.  
  15.  check io.Copy(w, r) 
  16.  check w.Close() 
  17.  return nil 

主函數(shù):

  1. func main() { 
  2.  handle err { 
  3.   log.Fatal(err) 
  4.  } 
  5.  
  6.  hex := check ioutil.ReadAll(os.Stdin) 
  7.  data := check parseHexdump(string(hex)) 
  8.  os.Stdout.Write(data) 

該提案引入了兩種新的語法形式,首先是 check 關鍵字,其可以選中一個表達式 check f(x, y, z) 或 check err,其將會標識這是一個顯式的錯誤檢查。

其次引入了 handle 關鍵字,用于定義錯誤處理程序流轉(zhuǎn),逐級上拋,依此類推,直到處理程序執(zhí)行 return 語句,才正式結束。

錯誤值打印(Error Printing)

第二個要解決的問題是錯誤值(Error Values)、錯誤檢查(Error Inspection)的問題,其引申出錯誤值打印(Error Printing)的問題,也可以認為是錯誤格式化的不便利。

官方針對此提出了提出了 Error Values 和 Error Printing 的草案設計。

簡單例子如下:

  1. if err != nil { 
  2.  return fmt.Errorf("write users database: %v", err) 

優(yōu)化后的方案如下:

  1. package errors 
  2.  
  3. type Wrapper interface { 
  4.  Unwrap() error 
  5.  
  6. func Is(err, target error) bool 
  7. func As(type E)(err error) (e E, ok bool) 

該提案增加了錯誤鏈的 Wrapping Error 概念,并同時增加 errors.Is 和 errors.As 的方法,與前面說到的 Go1.13 的改進一致,不再贅述。

需要留意的是,Go1.13 并沒有實現(xiàn) %+v 輸出調(diào)用堆棧的需求,因為此舉會破壞 Go1 兼容性和產(chǎn)生一些性能問題,大概會在 Go2 加入。

try-catch 不香嗎

社區(qū)中另外一股聲音就是直指 Go 語言反人類不用 try-catch 的機制,在社區(qū)內(nèi)也產(chǎn)生了大量的探討,具體可以看看相關的提案 Proposal: A built-in Go error check function, "try"。

目前該提案已被拒絕,具體可參見 go/issues/32437#issuecomment-512035919 和 Why does Go not have exceptions。

總結

在這篇文章中,我們介紹了目前 Go1 Error 的現(xiàn)狀,概括了大家對 Go 語言錯誤處理的常見問題和意見。

同時還介紹了在這幾年間,Go team 針對 Go2、Go1.13 Error 的持續(xù)優(yōu)化和探索。

 

責任編輯:武曉燕 來源: 腦子進煎魚了
相關推薦

2009-11-20 09:11:07

Chrome OS谷歌操作系統(tǒng)

2012-09-21 10:49:16

虛擬化

2014-09-01 10:22:29

Ubuntu

2010-10-20 08:53:57

Android 3.0

2019-12-26 12:00:24

ECUG Con 20

2009-02-20 08:51:22

.NET框架CLR組件

2011-03-09 10:45:09

DiscuzX2公測新功能

2015-07-30 10:05:37

Java9JShell

2009-10-28 12:27:36

linux操作系統(tǒng)發(fā)展

2011-04-08 16:14:21

2021-06-17 08:00:00

Windows 10Windows微軟

2013-03-25 09:51:53

Facebook數(shù)據(jù)中心云數(shù)據(jù)中心

2015-05-12 11:49:45

OpenStack K開源特性分析

2009-03-31 09:04:46

MacChrome瀏覽器

2011-11-30 08:41:20

NoSQL數(shù)據(jù)庫

2009-02-12 15:18:59

2010-11-17 11:25:20

高交會郵件安全263企業(yè)郵箱

2015-04-23 10:57:07

Apple WatchAPP

2011-04-01 11:26:21

JDK 7

2021-01-20 09:52:54

Windows 功能系統(tǒng)
點贊
收藏

51CTO技術棧公眾號