Go語言(golang)的錯誤(error)處理的推薦方案

原文連接:www.flysnow.org/2019/01/01/…
微信公衆號:flysnow_org(飛雪無情)html

對於Go語言(golang)的錯誤設計,相信不少人已經體驗過了,它是經過返回值的方式,來強迫調用者對錯誤進行處理,要麼你忽略,要麼你處理(處理也能夠是繼續返回給調用者),對於golang這種設計方式,咱們會在代碼中寫大量的if判斷,以便作出決定。java

func main() {
	conent,err:=ioutil.ReadFile("filepath")
	if err !=nil{
		//錯誤處理
	}else {
		fmt.Println(string(conent))
	}
}
複製代碼

這類代碼,在咱們編碼中是很是的,大部分狀況下error都是nil,也就是沒有任何錯誤,可是非nil的時候,意味着錯誤就出現了,咱們須要對他進行處理。git

error 接口

error其實一個接口,內置的,咱們看下它的定義github

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}
複製代碼

它只有一個方法 Error,只要實現了這個方法,就是實現了error。如今咱們本身定義一個錯誤試試。golang

type fileError struct {
}

func (fe *fileError) Error() string {
	return "文件錯誤"
}
複製代碼

自定義 error

自定義了一個fileError類型,實現了error接口。如今測試下看看效果。bash

func main() {
	conent, err := openFile()
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(string(conent))
	}
}

//只是模擬一個錯誤
func openFile() ([]byte, error) {
	return nil, &fileError{}
}
複製代碼

咱們運行模擬的代碼,能夠看到文件錯誤的通知。微信

在實際的使用過程當中,咱們可能遇到不少錯誤,他們的區別是錯誤信息不同,一種作法是每種錯誤都相似上面同樣定義一個錯誤類型,可是這樣太麻煩了。咱們發現Error返回的實際上是個字符串,咱們能夠修改下,讓這個字符串能夠設置就能夠了。函數

type fileError struct {
	s string
}

func (fe *fileError) Error() string {
	return fe.s
}
複製代碼

恩,這樣改造後,咱們就能夠在聲明fileError的時候,設置好要提示的錯誤文字,就能夠知足咱們不一樣的須要了。測試

//只是模擬一個錯誤
func openFile() ([]byte, error) {
	return nil, &fileError{"文件錯誤,自定義"}
}
複製代碼

恩,能夠了,已經達到了咱們的目的。如今咱們能夠把它變的更通用一些,好比修改fileError的名字,再建立一個輔助函數,便於咱們建立不一樣的錯誤類型。網站

//blog:www.flysnow.org
//wechat:flysnow_org
func New(text string) error {
	return &errorString{text}
}

type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}
複製代碼

變成以上這樣,咱們就能夠經過New函數,輔助咱們建立不一樣的錯誤了,這其實就是咱們常常用到的errors.New函數,被咱們一步步剖析演化而來,如今你們對Go語言(golang)內置的錯誤error有了一個清晰的認知了。

存在的問題

雖然Go語言對錯誤的設計很是簡潔,可是對於咱們開發者來講,很明顯是不足的,好比咱們須要知道出錯的更多信息,在什麼文件的,哪一行代碼?只有這樣咱們才更容易的定位問題。

還有好比,咱們想對返回的error附加更多的信息後再返回,好比以上的例子,咱們怎麼作呢?咱們只能先經過Error方法,取出原來的錯誤信息,而後本身再拼接,再使用errors.New函數生成新錯誤返回。

若是咱們之前作過java開發,咱們知道Java的異常是能夠嵌套的,也就是說,經過這個,咱們很容易知道錯誤的根本緣由,由於Java的異常,是一層層的嵌套返回的,無論中間經歷了多少包裝,咱們能夠經過cause找到根本錯誤的緣由。

解決問題

若是要解決以上的問題,那麼首先咱們必須再繼續擴充咱們的errorString,再增長一些字段來存儲更多的信息。好比咱們要記錄堆棧信息。

type stack []uintptr
type errorString struct {
	s string
	*stack
}
複製代碼

歡迎關注微信公衆號flysnow_org或者博客網站 www.flysnow.org/ 查看更多原創文章。

有了存儲堆棧信息的stack字段,咱們在生成錯誤的時候,就能夠把調用的堆棧信息存儲在這個字段裏。

//blog:www.flysnow.org
//wechat:flysnow_org

func callers() *stack {
	const depth = 32
	var pcs [depth]uintptr
	n := runtime.Callers(3, pcs[:])
	var st stack = pcs[0:n]
	return &st
}

func New(text string) error {
	return &errorString{
		s:   text,
		stack: callers(),
	}
}
複製代碼

完美解決,如今若是再解決,對現有的錯誤附加一些信息的問題呢?相信你們應該有思路了。

type withMessage struct {
	cause error
	msg   string
}

func WithMessage(err error, message string) error {
	if err == nil {
		return nil
	}
	return &withMessage{
		cause: err,
		msg:   message,
	}
}
複製代碼

使用WithMessage函數,對原來的error包裝下,就能夠生成一個新的帶有包裝信息的錯誤了。

推薦的方案

以上咱們在解決問題是,採起的方法是否是比較熟悉?尤爲是看源代碼,沒錯,這就是github.com/pkg/errors這個錯誤處理庫的源代碼。

由於Go語言提供的錯誤太簡單了,以致於簡單的咱們沒法更好的處理問題,甚至不能爲咱們處理錯誤,提供更有用的信息,因此誕生了不少對錯誤處理的庫,github.com/pkg/errors是比較簡潔的同樣,而且功能很是強大,受到了大量開發者的歡迎,使用者不少。

它的使用很是簡單,若是咱們要新生成一個錯誤,可使用New函數,生成的錯誤,自帶調用堆棧信息。

func New(message string) error 複製代碼

若是有一個現成的error,咱們須要對他進行再次包裝處理,這時候有三個函數能夠選擇。

//只附加新的信息
func WithMessage(err error, message string) error //只附加調用堆棧信息 func WithStack(err error) error //同時附加堆棧和信息 func Wrap(err error, message string) error 複製代碼

其實上面的包裝,很相似於Java的異常包裝,被包裝的error,其實就是Cause,在前面的章節提到錯誤的根本緣由,就是這個Cause。因此這個錯誤處理庫爲咱們提供了Cause函數讓咱們能夠得到最根本的錯誤緣由。

func Cause(err error) error {
	type causer interface {
		Cause() error
	}

	for err != nil {
		cause, ok := err.(causer)
		if !ok {
			break
		}
		err = cause.Cause()
	}
	return err
}
複製代碼

使用for循環一直找到最根本(最底層)的那個error

以上的錯誤咱們都包裝好了,也收集好了,那麼怎麼把他們裏面存儲的堆棧、錯誤緣由等這些信息打印出來呢?其實,這個錯誤處理庫的錯誤類型,都實現了Formatter接口,咱們能夠經過fmt.Printf函數輸出對應的錯誤信息。

%s,%v //功能同樣,輸出錯誤信息,不包含堆棧
%q //輸出的錯誤信息帶引號,不包含堆棧
%+v //輸出錯誤信息和堆棧
複製代碼

以上若是有循環包裝錯誤類型的話,會遞歸的把這些錯誤都會輸出。

小結

經過使用這個 github.com/pkg/errors 錯誤庫,咱們能夠收集更多的信息,可讓咱們更容易的定位問題。

咱們收集的這些信息不止能夠輸出到控制檯,也能夠當作日誌,使用輸出到相應的Log日誌裏,便於分析問題。

聽說這個庫,會被加入到Golang 標準 SDK 裏,期待着,若是加入的話,應該就是補充如今標準庫裏的errors 這個package了。

本文爲原創文章,轉載註明出處,歡迎掃碼關注公衆號flysnow_org或者網站asf www.flysnow.org/ ,第一時間看後續精彩文章。以爲好的話,請猛擊文章右下角「好看」,感謝支持。

掃碼關注
相關文章
相關標籤/搜索