前言
許多主流語言諸如:Java、Python都實現了try-catch-finally機制,而Go處理錯誤的方式卻與前兩種語言不同。關於Go處理異常的方式是好是壞仁者見仁智者見智,筆者還是更喜歡try-catch-fianlly的寫法,這里便和大家分享一個Go實現的try-catch-finally機制。下面先貼部分代碼的講解,完整代碼將在文章的末尾中給出。
Try-Catch-Finally
這里我們先來看一段代碼:
type CatchHandler interface { Catch(e error, handler func(err error)) CatchHandler CatchAll(handler func(err error)) FinalHandler FinalHandler } type FinalHandler interface { Finally(handlers ...func()) } func Try(f func()) CatchHandler { //返回一個實現CatchHandler接口的對象 f() //調用f函數 …… }
CatchHandler接口需要實現三個方法,一個是Catch(),這個方法用於接收一個特定類型的error以及error的處理器,返回還是CatchHandler類型,CatchAll()接收一個error處理器用於處理所有的錯誤,並返回一個FinalHandler類型的接口,而FinalHandler接口只實現一個方法Finally(),這個方法可接收多個處理器,用於在執行完try-catch塊之后執行。
這里有一些地方需要注意,按照我們的接口設計:Try函數返回CatchHandler接口對象,可以調用Catch()、CatchAll()、Finally()這三個方法,Catch()函數同樣返回CatchHandler接口對象,而輪到CatchAll()的時候返回的FinallyHandler接口對象,只有調用一個Finally()。綜上所述,未來我們的代碼將實現成這樣:
type Err1 struct { error } type Err2 struct { error } func main() { Try(func() { //業務代碼 }).Catch(Err1{}, func(err error) { //處理Err1類型的錯誤 }).Catch(Err2{}, func(err error) { //處理Err2類型的錯誤 }).CatchAll(func(err error) { //處理其他錯誤 }).Finally(func() { //處理完try-catch塊之后最終將會執行的的代碼 }) }
上面的設計也符合傳統try-catch-finally的寫法。
現在,我們給出Try函數的實現:
func Try(f func()) CatchHandler { t := &catchHandler{} defer func() { defer func() { //<2> r := recover() if r != nil { t.err = r.(error) } }() f() //<1> }() return t }
上面的代碼,在<1>處,我們將f()函數在一個defer塊中調用。同時在<2>處,又聲明了一個defer塊,用於捕捉異常。<1>處的defer塊會在Try()函數正常返回后(即return之后)調用,再執行返回后剩下的代碼。然后我們返回了結構體catchHandler的指針,說明結構體catchHandler必須實現接口CatchHandler所需要的函數。CatchHandler除了自身的兩個函數Catch()和CatchAll()之外,還繼承了FinalHandler的Finally()函數。所以統共需要實現的三個函數至少有三個。
下面,我們來看下catchHandler結構體的實現:
type catchHandler struct { err error hasCatch bool } //<1>RequireCatch函數有兩個作用:一個是判斷是否已捕捉異常,另一個是否發生了異常。如果返回false則代表沒有異常,或異常已被捕捉。 func (t *catchHandler) RequireCatch() bool { if t.hasCatch { //<2>如果已經執行了catch塊,就直接判斷不執行 return false } if t.err == nil { //<3>如果異常為空,則判斷不執行 return false } return true } func (t *catchHandler) Catch(e error, handler func(err error)) CatchHandler { if !t.RequireCatch() { return t } //<4>如果傳入的error類型和發生異常的類型一致,則執行異常處理器,並將hasCatch修改為true代表已捕捉異常 if reflect.TypeOf(e) == reflect.TypeOf(t.err) { handler(t.err) t.hasCatch = true } return t } func (t *catchHandler) CatchAll(handler func(err error)) FinalHandler { //<5>CatchAll()函數和Catch()函數都是返回同一個對象,但返回的接口類型卻不一樣,也就是CatchAll()之后只能調用Finally() if !t.RequireCatch() { return t } handler(t.err) t.hasCatch = true return t } func (t *catchHandler) Finally(handlers ...func()) { //<6>遍歷處理器,並在Finally函數執行完畢之后執行 for _, handler := range handlers { defer handler() } err := t.err //<7>如果異常不為空,且未捕捉異常,則拋出異常 if err != nil && !t.hasCatch { panic(err) } }
catchHandler有兩個字段:err和hasCatch,分別用於保存Try塊中函數執行之后返回的異常,以及異常是否被捕捉。
- <1>RequireCatch函數有兩個作用:一個是判斷是否已捕捉異常,另一個是否發生了異常。如果返回false則代表沒有異常,或異常已被捕捉。
- <2>如果已經執行了catch塊,就直接判斷不執行。
- <3>如果異常為空,則判斷不執行。
- <4>如果傳入的error類型和發生異常的類型一致,則執行異常處理器,並將hasCatch修改為true代表已捕捉異常。
- <5>CatchAll()函數和Catch()函數都是返回同一個對象,但返回的接口類型卻不一樣,也就是CatchAll()之后只能調用Finally()。
- <6>遍歷處理器,並在Finally函數執行完畢之后執行。
- <7>如果異常不為空,且未捕捉異常,則拋出異常。
現在,讓我們來執行下下面的main()函數:
type Err1 struct { error } type Err2 struct { error } func main() { //Try1 Try(func() { fmt.Println("Try1 start") panic(Err1{error: errors.New("error1")}) }).Catch(Err1{}, func(err error) { fmt.Println("Try1 Err1 Catch:", err.Error()) }).Catch(Err2{}, func(err error) { fmt.Println("Try1 Err2 Catch:", err.Error()) }).Finally(func() { fmt.Println("Try1 done") }) //Try2 Try(func() { fmt.Println("Try2 start") panic(Err2{error: errors.New("error2")}) }).Catch(Err1{}, func(err error) { fmt.Println("Try2 Err1 Catch:", err.Error()) }).CatchAll(func(err error) { fmt.Println("Try2 Catch All:", err.Error()) }).Finally(func() { fmt.Println("Try2 done") }) //Try3 Try(func() { fmt.Println("Try3 start") }).Catch(Err1{}, func(err error) { fmt.Println("Try3 Err1 Catch:", err.Error()) }).Finally(func() { fmt.Println("Try3 done") }) }
運行結果:
[root@bogon ]# go run main.go Try1 start Try1 Err1 Catch: error1 Try1 done Try2 start Try2 Catch All: error2 Try2 done Try3 start Try3 done
可以看到,這里確實實現了類似傳統try-catch-finally機制。另外有兩點需要注意:
- 強烈建議這里基於Go實現的Try-Catch機制都調用一下CatchAll(),即有可能出現Catch()函數無法匹配到的異常。
- 強烈建議這里的Try-Catch機制在不調用CatchAll()的情況下都要調用Finally(),即便你調用了Finally()也可以不塞任何處理函數進去。如果發生了異常,卻沒有捕捉到,傳統的try-catch-finally機制會在finally執行完畢后拋出異常,如果沒有finally塊則直接拋出異常,而這里需要執行Finally()函數后才能拋出,在Finally()函數中我們判斷有異常且異常未被捕捉,則會panic()出異常,如果發生異常未被捕捉,又沒有調用Finally(),那異常只能消失在歷史的洪流里啦!
最后,貼出完整代碼:
package main import ( "reflect" "fmt" "errors" ) type CatchHandler interface { Catch(e error, handler func(err error)) CatchHandler CatchAll(handler func(err error)) FinalHandler FinalHandler } type FinalHandler interface { Finally(handlers ...func()) } func Try(f func()) CatchHandler { t := &catchHandler{} defer func() { defer func() { r := recover() if r != nil { t.err = r.(error) } }() f() }() return t } type catchHandler struct { err error hasCatch bool } func (t *catchHandler) RequireCatch() bool { //<1>判斷是否有必要執行catch塊,true為需要執行,false為不執行 if t.hasCatch { //<2>如果已經執行了catch塊,就直接判斷不執行 return false } if t.err == nil { //<3>如果異常為空,則判斷不執行 return false } return true } func (t *catchHandler) Catch(e error, handler func(err error)) CatchHandler { if !t.RequireCatch() { return t } //<4>如果傳入的error類型和發生異常的類型一致,則執行異常處理器,並將hasCatch修改為true代表已捕捉異常 if reflect.TypeOf(e) == reflect.TypeOf(t.err) { handler(t.err) t.hasCatch = true } return t } func (t *catchHandler) CatchAll(handler func(err error)) FinalHandler { //<5>CatchAll()函數和Catch()函數都是返回同一個對象,但返回的接口類型卻不一樣,也就是CatchAll()之后只能調用Finally() if !t.RequireCatch() { return t } handler(t.err) t.hasCatch = true return t } func (t *catchHandler) Finally(handlers ...func()) { //<6>遍歷處理器,並在Finally函數執行完畢之后執行 for _, handler := range handlers { defer handler() } err := t.err //<7>如果異常不為空,且未捕捉異常,則拋出異常 if err != nil && !t.hasCatch { panic(err) } } type Err1 struct { error } type Err2 struct { error } func main() { //Try1 Try(func() { fmt.Println("Try1 start") panic(Err1{error: errors.New("error1")}) }).Catch(Err1{}, func(err error) { fmt.Println("Try1 Err1 Catch:", err.Error()) }).Catch(Err2{}, func(err error) { fmt.Println("Try1 Err2 Catch:", err.Error()) }).Finally(func() { fmt.Println("Try1 done") }) //Try2 Try(func() { fmt.Println("Try2 start") panic(Err2{error: errors.New("error2")}) }).Catch(Err1{}, func(err error) { fmt.Println("Try2 Err1 Catch:", err.Error()) }).CatchAll(func(err error) { fmt.Println("Try2 Catch All:", err.Error()) }).Finally(func() { fmt.Println("Try2 done") }) //Try3 Try(func() { fmt.Println("Try3 start") }).Catch(Err1{}, func(err error) { fmt.Println("Try3 Err1 Catch:", err.Error()) }).Finally(func() { fmt.Println("Try3 done") }) }