Go實現try-catch-finally機制


前言

許多主流語言諸如: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機制。另外有兩點需要注意

  1. 強烈建議這里基於Go實現的Try-Catch機制都調用一下CatchAll(),即有可能出現Catch()函數無法匹配到的異常。
  2. 強烈建議這里的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")
	})

}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM