原文連結:www.flysnow.org/2019/01/01/…
微信公眾號:flysnow_org(飛雪無情)
對於Go語言(golang)的錯誤設計,相信很多人已經體驗過了,它是通過返回值的方式,來強迫呼叫者對錯誤進行處理,要麼你忽略,要麼你處理(處理也可以是繼續返回給呼叫者),對於golang這種設計方式,我們會在程式碼中寫大量的if
判斷,以便做出決定。
func main() {
conent,err:=ioutil.ReadFile("filepath")
if err !=nil{
//錯誤處理
}else {
fmt.Println(string(conent))
}
}
複製程式碼
這類程式碼,在我們編碼中是非常的,大部分情況下error
都是nil
,也就是沒有任何錯誤,但是非nil
的時候,意味著錯誤就出現了,我們需要對他進行處理。
error 介面
error
其實一個介面,內建的,我們看下它的定義
// 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
。現在我們自己定義一個錯誤試試。
type fileError struct {
}
func (fe *fileError) Error() string {
return "檔案錯誤"
}
複製程式碼
自定義 error
自定義了一個fileError
型別,實現了error
介面。現在測試下看看效果。
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/ ,第一時間看後續精彩文章。覺得好的話,請猛擊文章右下角「好看」,感謝支援。