Go異常處理

      網(wǎng)友投稿 638 2025-04-01

      一、Error vs Exception

      在這部分開頭先講一下,在Go語言中沒有Exception的概念,只有Error,這一點(diǎn)區(qū)別于Python、Java等這些語言。至于為什么沒有Exception后邊來說,現(xiàn)在我們先看一下Go中的error.go的源碼

      package errors // New returns an error that formats as the given text. // Each call to New returns a distinct error value even if the text is identical. func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return e.s }

      源碼具體分析:

      ? 在errors.go的源碼包中定義很簡(jiǎn)單,一個(gè)結(jié)構(gòu)體,兩個(gè)方法。結(jié)構(gòu)體中一個(gè)字段,string類型。

      ? Error方法又一個(gè)指針類型的方法接收器,返回的值是結(jié)構(gòu)體中的字段

      ? New方法沒有方法接收器,參數(shù)是一個(gè)string類型,返回的是結(jié)構(gòu)體的內(nèi)存地址,這里為什么會(huì)返回呢?因?yàn)槿绻麉?shù)不同的程序傳入來相同內(nèi)容的text的參數(shù),如果返回的不是內(nèi)存地址,那么兩個(gè)error是完全相等的,但兩個(gè)程序又是不同的程序,所以會(huì)出現(xiàn)問題。在計(jì)算機(jī)中會(huì)為每一個(gè)變量專門開辟出一塊獨(dú)立的內(nèi)存空間用于存儲(chǔ),所以每一個(gè)變量的內(nèi)存地址是不一樣的,通過返回內(nèi)存地址就可以區(qū)分兩個(gè)不同程序的error。

      通過上邊的源碼分析,我們現(xiàn)在自己編寫一個(gè)程序,對(duì)比一下:

      ? ErrNamedType調(diào)用的自定義的New()返回的是字符串,所以ErrNamedType == New(“EOF”)是true

      ? ErrStructType()調(diào)用的是errors.New()返回的是內(nèi)存地址,所以ErrStructType == errors.New(“EOF”)收false

      import ( "errors" "fmt" ) type errorString string func (e errorString) Error() string { return string(e) // 類型的強(qiáng)制轉(zhuǎn)換 } func New(text string) error { return errorString(text) // 返回的是字符串 } var ErrNamedType = New("EOF") var ErrStructType = errors.New("EOF") func main() { if ErrNamedType == New("EOF") { // 這里兩個(gè)都是自定義的New("EOF")方法的返回值,所以是相等的 fmt.Println("Named Type Error") } if ErrStructType == errors.New("EOF") { // 這里不相等,是因?yàn)闃?biāo)準(zhǔn)庫中的errors.New()方法返回的是指針,return &errorString{text}即使text完全相等 // 返回的結(jié)果也不想等 fmt.Println("Struct Type Error") } }

      Go語言中為什么不使用Exception?回答這個(gè)問題前我們先看一下幾個(gè)主流語言的演進(jìn):

      ? C:?jiǎn)畏祷刂担话阃ㄟ^傳遞指針作為入?yún)ⅲ祷刂禐閕nt表示成功還是失敗,這么做的好處是int可以表示更多的錯(cuò)誤結(jié)果

      ? C++:引入了exception,但是無法知道被調(diào)用方會(huì)拋出什么異常

      ? Java:引入了checked exception,方法的所有者必須聲明,調(diào)用者必須處理。在啟動(dòng)時(shí)拋出大量的異常是司空見慣的事情,并在他們呢的調(diào)用堆棧中盡職的記錄下來。Java異常不再是異常,而是變得司空見慣了。它們從良性到災(zāi)難性都有使用,異常的嚴(yán)重性由函數(shù)的調(diào)用者來區(qū)分。

      在Go語言中,處理邏輯是不引入exception,支持多參數(shù)返回,所以很容易的在函數(shù)簽名中帶上實(shí)現(xiàn)來err interface的對(duì)象,交由調(diào)用者來判定。如果一個(gè)函數(shù)返回了value和error,那么必須先判定error是否為nil,不能直接對(duì)value的值做任何的假設(shè)。唯一可以忽略error的情況是,調(diào)用者對(duì)value都不關(guān)心。

      例如:

      package main import "fmt" func handle() (int, error) { return 1, nil // 多參數(shù)返回 } func main() { i, err := handle() if err != nil { // 首先處理err return } fmt.Println(i) }

      Go中有panic機(jī)制,panic機(jī)制和其他語言的exception不同,其他語言的exception是把異常向上拋,讓調(diào)用者來處理。而panic則意味著fatal error(程序直接掛掉了),調(diào)用的是底層的os.Exit。不能假設(shè)調(diào)用者來解決panic,意味著代碼不能繼續(xù)進(jìn)行。使用多個(gè)返回值和一個(gè)簡(jiǎn)單的約定,Go讓程序員知道什么時(shí)候出了問題,并為真正的異常情況保留了panic。

      總結(jié):對(duì)于真正意外的情況,那些表示不可恢復(fù)的程序錯(cuò)誤,例如索引越界、不可恢復(fù)的環(huán)境問題、棧溢出,才能使用panic。對(duì)于其他的情況,應(yīng)該使用error來進(jìn)行判定。

      you need to check the error value if you care about the result. --Dave

      二、錯(cuò)誤類型

      預(yù)定義的特定錯(cuò)誤,被成為sentinel error,這個(gè)名字來源于計(jì)算機(jī)編程中使用一個(gè)特定的值來表示不可能進(jìn)一步處理的做法。所以,對(duì)于Go來說使用特定的值來表示錯(cuò)誤。"if err == ErrSomething{…}類似的io.EOF,更底層的syscall.ENOENT。

      使用sentinel值是最不靈活的錯(cuò)誤處理策略,因?yàn)檎{(diào)用方必須使用 == 將結(jié)果與預(yù)先聲明的值進(jìn)行比較。當(dāng)想要提供更多的上下文時(shí),就出現(xiàn)了一個(gè)問題,因?yàn)榉祷匾粋€(gè)錯(cuò)誤就會(huì)破壞相等性檢查。甚至是一些有意義的fmt.Errorf攜帶一些上下文,也會(huì)破壞調(diào)用者的 ==,調(diào)用者將被迫查看error.Error()方法的輸出,以查看它是否與特定的字符串相匹配。

      注意事項(xiàng):

      ? 不依賴檢查error.Error的輸出

      不應(yīng)該依賴檢測(cè) error.Error 的輸出,Error 方法存在于 error 接口主要用于方便程序員使用,但不是程序(編寫測(cè)試可能會(huì)依賴這個(gè)返回)。這個(gè)輸出的字符串用于記錄日志、輸出到 stdout 等。

      ? Sentinel errors 成為你 API 公共部分。

      如果您的公共函數(shù)或方法返回一個(gè)特定值的錯(cuò)誤,那么該值必須是公共的,當(dāng)然要有文檔記錄,這會(huì)增加 API 的表面積。

      如果 API 定義了一個(gè)返回特定錯(cuò)誤的 interface,則該接口的所有實(shí)現(xiàn)都將被限制為僅返回該錯(cuò)誤,即使它們可以提供更具描述性的錯(cuò)誤。

      比如 io.Reader。像 io.Copy 這類函數(shù)需要 reader 的實(shí)現(xiàn)者比如返回 io.EOF 來告訴調(diào)用者沒有更多數(shù)據(jù)了,但這又不是錯(cuò)誤

      在做基礎(chǔ)庫開發(fā)的時(shí)候,一定是盡可能少的暴露公共API,越多的暴露,則整個(gè)基礎(chǔ)庫/系統(tǒng)越脆弱。

      ? Sentinel errors在兩個(gè)包之間創(chuàng)建了依賴關(guān)系

      Sentinel errors最糟糕的問題是在源代碼包和引入Sentinel errors的包之間創(chuàng)建了依賴的關(guān)系。例如:檢查錯(cuò)誤是否等于io.EOF,那么代碼之中必須引入io。雖然這種引用很常見,但是這么操作無疑是增加了項(xiàng)目中的耦合度。

      ? 結(jié)論是:盡可能避免sentinel errors的使用。

      Error types這種類型錯(cuò)誤是通過自定義一個(gè)error結(jié)構(gòu)體,在結(jié)構(gòu)體內(nèi)有不同的字段信息,這些信息描述了錯(cuò)誤的上下文,當(dāng)錯(cuò)誤發(fā)生時(shí),使用switch+類型斷言判斷是否調(diào)用,以及返回什么錯(cuò)誤信息。與Sentinel error相比,錯(cuò)誤類型的一大改進(jìn)是他們能夠包裝底層的錯(cuò)誤以提供更多的上下文。一個(gè)不錯(cuò)的例子就是os.PathError它提供了底層執(zhí)行什么操作、哪個(gè)路徑出現(xiàn)了問題。

      這種類型錯(cuò)誤,調(diào)用者需要使用類型斷言和類型switch,就要讓自定義的error變成了public。這種模型會(huì)導(dǎo)致和調(diào)用者之間產(chǎn)生強(qiáng)耦合,從而導(dǎo)致API變?nèi)酢?/p>

      package day11 import ( "fmt" "testing" ) // 自定義Error Type type MyError struct { Msg string // 錯(cuò)誤信息 File string // 在哪個(gè)文件 Line int // 哪一行發(fā)生錯(cuò)誤 } func (e *MyError) Error() string { return fmt.Sprintf("%s:%d:%s", e.File, e.Line, e.Msg) } func test() error { return &MyError{"Something happened", "error_type_test.go", 44} } func TestErrorType(t *testing.T) { err := test() switch err := err.(type) { // switch+斷言判斷錯(cuò)誤 case nil: case *MyError: fmt.Println("error occurred on line: ", err.Line) // 類型斷言成功之后就可以調(diào)用自定義Error結(jié)構(gòu)體內(nèi)的任意字段 default: } }

      結(jié)論:盡量避免使用error types,雖然錯(cuò)誤類型比sentinel errors更好,因?yàn)樗鼈兛梢圆东@關(guān)于錯(cuò)誤的更多上下文(字段信息),但是error types共享error values許多相同的問題。因此,應(yīng)盡量避免錯(cuò)誤類型,或者至少避免將它們作為公共API的一部分。

      Opaque errors(不透明的錯(cuò)誤)相比較而言是最透明的、最佳的錯(cuò)誤處理策略,它要求代碼和調(diào)用者之間的耦合最少。因?yàn)殡m然調(diào)用者知道發(fā)生了錯(cuò)誤,但是卻沒有能力獲知錯(cuò)誤的內(nèi)部信息。作為調(diào)用者關(guān)于操作結(jié)果所知道的也僅僅是它是否起作用了(成功還是失敗)。

      Assert errors for behaviour, not type. 這句話的意思是斷言錯(cuò)誤的行為,而不是類型。在少數(shù)的情況下,這種二分的錯(cuò)誤處理方法是不夠的。例如:與進(jìn)程外的世界交互(入網(wǎng)絡(luò)活動(dòng)),需要調(diào)用方調(diào)查錯(cuò)誤的性質(zhì),以確定重試該操作是否合理。在這種情況下,我們可以斷言錯(cuò)誤實(shí)現(xiàn)了特定的行為,而不是斷言錯(cuò)誤是特定的類型或值。

      代碼示例:

      package main import "fmt" type MyError struct { Msg string File string Line int } // 自定義一個(gè)私有的接口 type temporary interface { Temporary() bool } func IsTemporary(err error) bool { te, ok := err.(temporary) // 斷言 return ok && te.Temporary() } func test() error { return &MyError{"Something happened", "error_type_test.go", 44} } func main() { err := test() fmt.Println(IsTemporary(err)) } // 運(yùn)行結(jié)果:false

      三、錯(cuò)誤的處理

      ? 盡量使用if err != nil,而不是if err == nil,如果使用后者,大量的正常代碼都要在{}內(nèi),看著很不舒服。

      ? 日志記錄與錯(cuò)誤無關(guān)且對(duì)調(diào)試沒有幫助的信息應(yīng)該被視為噪音,應(yīng)該予以質(zhì)疑。記錄的原因是某些東西失敗了,而日志包含了答案。

      ? 錯(cuò)誤要被日志記錄。應(yīng)用程序處理錯(cuò)誤,保證100%的完整性,并且處理之后不再報(bào)當(dāng)前錯(cuò)誤。

      ? 當(dāng)出現(xiàn)錯(cuò)誤的時(shí)候,如果當(dāng)前程序可以處理應(yīng)立即處理,并且一個(gè)錯(cuò)誤只能處理一次,處理完之后錯(cuò)誤應(yīng)該立即降級(jí);如果不能處理,則應(yīng)該把錯(cuò)誤的詳細(xì)信息向上層拋,讓上層去解決,如果上層也不能解決,則繼續(xù)向上拋。

      ? 為什么要使用第三方的errors包呢?因?yàn)镚o語言自帶的errors包,包括fmt打印日志不能提供原始錯(cuò)誤的堆棧信息(上下文信息),也沒有完整的錯(cuò)誤鏈路。如果使用fmt打印錯(cuò)誤日志,一個(gè)error在經(jīng)過不同的方法之后,每一個(gè)方法都要打印一次,直到最頂層時(shí)去查看錯(cuò)誤會(huì)發(fā)現(xiàn)兩問題:1. 沒有錯(cuò)誤的詳細(xì)信息;2. 錯(cuò)誤的日志分散在各處,不好排查錯(cuò)誤。而第三方包pkg/errors提供了包裝原始錯(cuò)誤的方法,可以直接把包含堆棧信息的原始錯(cuò)誤包裝之后返回去,等到最上層的時(shí)候直接可以打印出錯(cuò)誤的詳細(xì)信息,利于排查問題。

      ? 基本用法:

      ? 導(dǎo)入:go get github.com/pkg/errors

      ? errors.Wrap(錯(cuò)誤,添加的信息)

      Go異常處理

      ? errors.WithMessage(錯(cuò)誤,添加的信息)

      ? errors.Cause(err)用于獲取錯(cuò)誤原始錯(cuò)誤,拿到原始錯(cuò)誤(根因)后就可以使用Sentinel或者error types了。 %T獲取類型,%v獲取值,%+v獲取錯(cuò)誤原始的堆棧信息。

      errors.Wrap()和errors.WithMessage()這兩個(gè)方法的作用都是添加信息,包裝錯(cuò)誤的原始錯(cuò)誤,包裝后的信息更加的清晰、易讀。不同點(diǎn)在于:errors.Warp()會(huì)攜帶錯(cuò)誤完整的堆棧信息,而errors.WithMessage()卻沒有。還有一點(diǎn)需要注意,在我們?nèi)粘i_發(fā)時(shí),應(yīng)用的代碼(業(yè)務(wù)代碼)中,可以直接使用errors.New()或者errors.Errorf()方法返回錯(cuò)誤信息,這兩個(gè)方法包含有堆棧信息,這么做也符合我們一個(gè)錯(cuò)誤處理一次的原則。

      package day11 import ( "bufio" "fmt" "github.com/pkg/errors" "io" "os" "testing" ) func ReadFile02(f io.Reader) ([]string, error) { var ( se = bufio.NewScanner(f) lines int content []string ) for se.Scan(){ content = append(content, se.Text()) lines++ } return content, se.Err() } func ReadFile(path string) ([]string, error) { f, err := os.Open(path) // 類似于Python中的獲取文件句柄 if err != nil { return nil, errors.Wrap(err, "open failed") // 包裝原始錯(cuò)誤,包含堆棧信息 } defer f.Close() content, err := ReadFile02(f) if err != nil { return nil, errors.Wrap(err, "get content failed") } return content, err } func TestReadFile(t *testing.T) { con, err := ReadFile("/Users/apple/workplace/go_test/src/day11/test.txt") if err != nil { fmt.Printf("original error: %T %v\n", errors.Cause(err), errors.Cause(err)) // 獲取原始錯(cuò)誤的類型和值 fmt.Printf("stack trace: \n%+v\n", err) // 獲取錯(cuò)誤原始的堆棧信息 } for i := 0; i < len(con); i++ { fmt.Println(con[i]) } os.Exit(1) }

      Go

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:Word2016進(jìn)行按照姓氏筆畫進(jìn)行排序(word姓名按筆畫排序)
      下一篇:生產(chǎn)計(jì)劃優(yōu)化系統(tǒng)(生產(chǎn)計(jì)劃優(yōu)化系統(tǒng) APC)
      相關(guān)文章
      亚洲网址在线观看| 在线观看亚洲网站| 久久综合亚洲色HEZYO社区| 亚洲人成无码网WWW| 蜜芽亚洲av无码一区二区三区| 亚洲一区二区免费视频| 亚洲成a人片毛片在线| 亚洲人成电影亚洲人成9999网| 亚洲熟妇中文字幕五十中出| 亚洲女同成人AⅤ人片在线观看| 日韩精品亚洲专区在线影视| 日韩欧美亚洲中文乱码| 亚洲成AV人片高潮喷水| 亚洲av无码片vr一区二区三区| 亚洲精品无码mⅴ在线观看| 亚洲国产午夜精品理论片在线播放 | 亚洲乱码中文字幕小综合| 亚洲日韩乱码中文无码蜜桃臀 | 欧美色欧美亚洲另类二区| 相泽南亚洲一区二区在线播放| 毛片亚洲AV无码精品国产午夜| 午夜亚洲WWW湿好爽| 亚洲А∨精品天堂在线| 亚洲精品视频在线看| 亚洲中文字幕久久精品无码喷水 | 亚洲综合日韩久久成人AV| 亚洲区小说区激情区图片区| 亚洲国产精品无码久久一线 | 亚洲精品美女久久7777777| 亚洲AV色无码乱码在线观看| 国产成人亚洲综合无| 久久乐国产精品亚洲综合| 国产AV无码专区亚洲AWWW| 亚洲av无码成人黄网站在线观看| 亚洲国产精品久久| 亚洲丰满熟女一区二区v| 亚洲天然素人无码专区| jjzz亚洲亚洲女人| 亚洲日韩精品射精日| 亚洲欧洲在线观看| 亚洲娇小性xxxx色|