go中的error小結


go中的error

go中的錯誤處理,是通過返回值的形式來出來,要么你忽略,要么你處理(處理也可以是繼續返回給調用者),對於golang這種設計方式,我們會在代碼中寫大量的if判斷,以便做出決定。

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

對於err如果是nil就代表沒有錯誤,如果不是nil就代表程序出問題了,需要對錯誤進行處理了。

error和panic

error:可預見的錯誤

panic:不可預見的異常

需要注意的是,你應該盡可能地使用error,而不是使用 panicrecover。只有當程序不能繼續運行的時候,才應該使用 panicrecover 機制。

panic 有兩個合理的用例。

1、發生了一個不能恢復的錯誤,此時程序不能繼續運行。 一個例子就是 web 服務器無法綁定所要求的端口。在這種情況下,就應該使用 panic,因為如果不能綁定端口,啥也做不了。

2、發生了一個編程上的錯誤。 假如我們有一個接收指針參數的方法,而其他人使用 nil 作為參數調用了它。在這種情況下,我們可以使用 panic,因為這是一個編程錯誤:用 nil 參數調用了一個只能接收合法指針的方法。

在一般情況下,我們不應通過調用panic函數來報告普通的錯誤,而應該只把它作為報告致命錯誤的一種方式。當某些不應該發生的場景發生時,我們就應該調用panic。

總結下panic的使用場景

1、空指針引用
2、下標越界
3、除數為0
4、不應該出現的分支,比如default
5、輸入不應該引起函數錯誤

error接口

go中的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
}

errors.New()是我們會經常使用的,我們來探究下這個函數

// src/errors/errors.go

func New(text string) error {
	return &errorString{text}
}

type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

使用 New 函數創建出來的 error 類型實際上是 errors 包里未導出的 errorString 類型,它包含唯一的一個字段 s,並且實現了唯一的方法:Error() string。

舉個使用的栗子

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // implementation
}

我們可以使用errors.New來定制我們需要的錯誤信息

但是對於上面的報錯,我們知道是不知道報錯的上下文信息的,我們就知道程序出錯了,不利於我們錯誤的排查。我們可以使用fmt.Errorf來輸出上下文信息。

if f < 0 {
    return 0, fmt.Errorf("math: square root of negative number %g", f)
}

通過fmt.Errorf我們不僅能打印錯誤,同時還能看到具體什么數數值引起的錯誤。它會先將字符串格式化,然后再調用errors.New來創建錯誤。

當我們想知道錯誤類型,並且打印錯誤的時候,直接打印 error

fmt.Println(err)

或者:

fmt.Println(err.Error)

fmt 包會自動調用 err.Error() 函數來打印字符串。

注意:對於err我們都是將err放在函數返回值的最后一個,同時對於會出錯的函數我們都會返回一個err,當然對於一些函數,我們可能不確定之后是否會有錯誤的產生,所以一般也是預留一個err的返回。

go中err的困局

在go中,err的是通過返回值的形式返回。編程人員,要不處理,要不忽略。所以我們的代碼就會大量的出現對錯誤的if判斷。
出於對代碼的健壯性考慮,我們對於每一個錯誤,都是不能忽略的。因為出錯的同時,很可能會返回一個 nil 類型的對象。如果不對錯誤進行判斷,那下一行對 nil 對象的操作百分之百會引發一個 panic
所以就造成了err滿天飛。

還有比如,我們想對返回的error附加更多的信息后再返回,比如以上的例子,我們怎么做呢?我們只能先通過Error方法,取出原來的錯誤信息,然后自己再拼接,再使用errors.New函數生成新錯誤返回。

推薦方法

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

這個錯誤處理庫為我們提供了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 //輸出錯誤信息和堆棧

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

總結

對於err我們都是將err放在函數返回值的最后一個,同時對於會出錯的函數我們都會返回一個err,當然對於一些函數,我們可能不確定之后是否會有錯誤的產生,所以一般也是預留一個err的返回。

Go 中的 error 過於簡單,以至於無法記錄太多的上下文信息,對於錯誤包裹也沒有比較好的辦法。當然,這些可以通過第三方庫來解決。

Go 語言使用 errorpanic 處理錯誤和異常是一個非常好的做法,比較清晰。至於是使用 error 還是 panic,看具體的業務場景。

參考

【Go語言(golang)的錯誤(error)處理的推薦方案】https://www.flysnow.org/2019/01/01/golang-error-handle-suggestion.html
【Golang error 的突圍】https://www.cnblogs.com/qcrao-2018/p/11538387.html
【The Go Blog Error handling and Go】https://blog.golang.org/error-handling-and-go
【Go 1.13 errors 基本用法】https://segmentfault.com/a/1190000020398774
【Go與Error的前世今生】https://zhuanlan.zhihu.com/p/55975116
【Go 系列教程 —— 32. panic 和 recover】https://studygolang.com/articles/12785
【Golang錯誤和異常處理的正確姿勢】https://www.jianshu.com/p/f30da01eea97


免責聲明!

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



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