Go語言標(biāo)準(zhǔn)錯(cuò)誤error全面解析
錯(cuò)誤類型
errorString
錯(cuò)誤是程序中處理邏輯和系統(tǒng)穩(wěn)定新的重要組成部分。
在go語言中內(nèi)置錯(cuò)誤如下:
// 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類型是一個(gè)接口類型,內(nèi)含一個(gè)Error的方法。它是錯(cuò)誤的頂級(jí)接口,實(shí)現(xiàn)了此內(nèi)置方法的結(jié)構(gòu)體都是其子類。
errorString結(jié)構(gòu)體是內(nèi)置實(shí)現(xiàn)錯(cuò)誤接口的內(nèi)置實(shí)現(xiàn),源碼如下:
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}New方法是內(nèi)置錯(cuò)誤類型實(shí)現(xiàn)。
errors.New()是最常用的錯(cuò)誤類實(shí)現(xiàn)方法。
wrapError
wrapError是error的另一種實(shí)現(xiàn)類,位于fmt的包中,源碼如下:
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand.
// If there is more than one %w verb, the returned error will implement an
// Unwrap method returning a []error containing all the %w operands in the
// order they appear in the arguments.
// It is invalid to supply the %w verb with an operand that does not implement
// the error interface. The %w verb is otherwise a synonym for %v.
func Errorf(format string, a ...any) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
switch len(p.wrappedErrs) {
case 0:
err = errors.New(s)
case 1:
w := &wrapError{msg: s}
w.err, _ = a[p.wrappedErrs[0]].(error)
err = w
default:
if p.reordered {
slices.Sort(p.wrappedErrs)
}
var errs []error
for i, argNum := range p.wrappedErrs {
if i > 0 && p.wrappedErrs[i-1] == argNum {
continue
}
if e, ok := a[argNum].(error); ok {
errs = append(errs, e)
}
}
err = &wrapErrors{s, errs}
}
p.free()
return err
}
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}wrapError是另一個(gè)內(nèi)置錯(cuò)誤實(shí)現(xiàn)類,使用%w作為占位符,這里的wrapError實(shí)現(xiàn)了Unwrap方法,用戶返回內(nèi)置的err即嵌套的err。
wrapError還有一個(gè)復(fù)數(shù)形式wrapErrors這里不再過多贅述。
- 自定義錯(cuò)誤
實(shí)現(xiàn)自定義錯(cuò)誤非常簡(jiǎn)單,面向?qū)ο蟮奶匦詫?shí)現(xiàn)錯(cuò)誤接口erros就是實(shí)現(xiàn)了錯(cuò)誤類。安裝go語言的繼承的特性,實(shí)現(xiàn)接口對(duì)應(yīng)的方法即可。
type error interface {
Error() string
}type MyErr struct {
e string
}
func (s *MyErr) Error() string {
return s.e
}
func main(){
var testErr error
testErr = &MyErr{"err"}
fmt.Println(testErr.Error())
}上述代碼就是實(shí)現(xiàn)了一個(gè)自定義的error類型,注意它是一個(gè)結(jié)構(gòu)體,實(shí)際上是errorString的子類。
新建錯(cuò)誤
上一小節(jié)介紹了三種錯(cuò)誤類型,前兩中是內(nèi)置的錯(cuò)誤類型,其中自定義的錯(cuò)誤是可拓展的,可以實(shí)現(xiàn)前兩種的任意一個(gè)。
第一種errorString是實(shí)現(xiàn)比較方便,只有實(shí)現(xiàn)Error()方法;
第二種是wrapError需要實(shí)現(xiàn)兩種方法,還有一種是Unwrap()。
errors.New()
err := errors.New("this is a error")
fmt.Printf("----%T----%v\n", err, err)該方法創(chuàng)建的是errorString類實(shí)例
fmt.Errorf()
err = fmt.Errorf("err is: %v", "no found")
fmt.Println(err)該方法創(chuàng)建的是wrapError類實(shí)例,wrapError也是errorString的子類。
實(shí)例化
type MyErr struct {
e string
}
func (s *MyErr) Error() string {
return s.e
}
func main(){
var testErr error
testErr = &MyErr{"err"}
fmt.Println(testErr.Error())
}由于自定義錯(cuò)誤一般需要錯(cuò)誤信息,所以一般直接構(gòu)造方法實(shí)例化。
錯(cuò)誤解析
errors.Is()
errors.Is 用于判斷一個(gè)錯(cuò)誤是否與另一個(gè)特定的錯(cuò)誤相等。它不僅僅是簡(jiǎn)單的比較錯(cuò)誤的值,還會(huì)檢查錯(cuò)誤鏈中的所有錯(cuò)誤,看看它們是否與給定的目標(biāo)錯(cuò)誤匹配。
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findItem(id int) error {
if id == 0 {
return ErrNotFound
}
return nil
}
func main() {
err := findItem(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("Item not found")
} else {
fmt.Println("Item found")
}
}
注意這里有一個(gè)坑,就是Is方法判斷的錯(cuò)誤的類型和錯(cuò)誤的信息,使用New方法即使構(gòu)建的錯(cuò)誤信息相同類型不一樣也是不相等的,如下:
err1 := errors.New("err1")
err2 := errors.New("err1")
err := errors.Is(err1, err2)
fmt.Println(err) // 輸出: falseerrors.As()
errors.As 用于將一個(gè)錯(cuò)誤轉(zhuǎn)換為特定的錯(cuò)誤類型。如果錯(cuò)誤鏈中的某個(gè)錯(cuò)誤匹配給定的目標(biāo)類型,那么 errors.As 會(huì)將該錯(cuò)誤轉(zhuǎn)換為該類型,并將其賦值給目標(biāo)變量。
package main
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("code %d: %s", e.Code, e.Msg)
}
func doSomething() error {
return &MyError{Code: 404, Msg: "Not Found"}
}
func main() {
err := doSomething()
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("Custom error: %v (Code: %d)\n", myErr.Msg, myErr.Code)
} else {
fmt.Println("Unknown error")
}
}
可用作類型判斷。
errors.Unwrap()
errors.Unwrap() 是一個(gè)用于處理嵌套或包裝錯(cuò)誤的函數(shù)。它的主要作用是提取并返回一個(gè)錯(cuò)誤的直接底層錯(cuò)誤(即被包裝的錯(cuò)誤),如果該錯(cuò)誤沒有被包裝過,則返回 nil。
package main
import (
"errors"
"fmt"
)
func main() {
baseErr := errors.New("base error")
wrappedErr := fmt.Errorf("wrapped error: %w", baseErr)
// 使用 errors.Unwrap 來提取底層錯(cuò)誤
unwrappedErr := errors.Unwrap(wrappedErr)
fmt.Println("Wrapped error:", wrappedErr)
fmt.Println("Unwrapped error:", unwrappedErr)
}
// Output
Wrapped error: wrapped error: base error
Unwrapped error: base error該方法只能獲取錯(cuò)誤的直接內(nèi)嵌錯(cuò)誤,如果要獲取更深層次的錯(cuò)誤需要便利判斷。
注意:
errors.Is: 用于判斷一個(gè)錯(cuò)誤是否與特定的錯(cuò)誤值相等。適合在錯(cuò)誤鏈中查找某個(gè)特定的錯(cuò)誤(比如一個(gè)已知的預(yù)定義錯(cuò)誤)。errors.As: 用于將錯(cuò)誤轉(zhuǎn)換為特定的錯(cuò)誤類型。適合當(dāng)你需要根據(jù)錯(cuò)誤的具體類型來處理錯(cuò)誤時(shí)使用。errors.Unwrap()用于從一個(gè)包裝錯(cuò)誤中提取并返回底層的錯(cuò)誤。如果錯(cuò)誤沒有被包裝過(或者沒有實(shí)現(xiàn) Unwrap 方法),它會(huì)返回nil。
錯(cuò)誤處理
if err := findAll(); err != nil {
// logic
}這個(gè)處理過程是不是很眼熟,利用錯(cuò)誤解析小節(jié)的處理方法可以對(duì)錯(cuò)誤判斷,進(jìn)行后續(xù)的處理。
go語言也提供了錯(cuò)誤捕獲recover的機(jī)制和錯(cuò)誤拋出panic機(jī)制。
panic
panic 是 Go 語言中的一種觸發(fā)異常處理機(jī)制的方式。當(dāng)你調(diào)用 panic 時(shí),程序會(huì)立刻停止執(zhí)行當(dāng)前函數(shù),并從調(diào)用棧中逐層向上拋出,直到找到一個(gè)適合的 recover,或者最終導(dǎo)致程序崩潰。
package main
import "fmt"
func main() {
fmt.Println("Start")
panic("Something went wrong!")
fmt.Println("End") // This line will not be executed
}當(dāng)程序中調(diào)用了 panic,程序會(huì)立刻中止當(dāng)前的控制流,開始回溯棧幀,并執(zhí)行每一層的 defer 語句。在執(zhí)行完所有的 defer 語句后,如果沒有遇到 recover,程序?qū)⒈罎?,并打?panic 的信息和堆棧跟蹤。
recover
recover 是一個(gè)內(nèi)建函數(shù),用于恢復(fù) panic。它只能在 defer 函數(shù)中有效。當(dāng) panic 發(fā)生時(shí),如果當(dāng)前函數(shù)或任何調(diào)用棧上的函數(shù)中有 defer 函數(shù)調(diào)用了 recover,那么可以捕獲 panic 的內(nèi)容,并使程序恢復(fù)正常執(zhí)行。
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from:", r)
}
}()
fmt.Println("Start")
panic("Something went wrong!")
fmt.Println("End") // This line will not be executed
}
recover 通常用于確保某些代碼塊即使發(fā)生了 panic,也能執(zhí)行資源清理操作,并避免整個(gè)程序崩潰。
defer
defer 語句用于延遲函數(shù)的執(zhí)行,直到包含 defer 語句的函數(shù)執(zhí)行完畢時(shí),才會(huì)執(zhí)行。這通常用于確保資源釋放或清理操作(如關(guān)閉文件、解鎖互斥鎖等)即使在函數(shù)中發(fā)生錯(cuò)誤或提前返回時(shí)也會(huì)被執(zhí)行。
- 執(zhí)行順序: 在一個(gè)函數(shù)中,你可以有多個(gè) defer 語句。這些 defer 調(diào)用的執(zhí)行順序是后進(jìn)先出(LIFO)。也就是說,最后一個(gè) defer 聲明的語句會(huì)最先執(zhí)行。
- 捕獲值: defer 語句會(huì)在聲明時(shí)捕獲它引用的變量的值。也就是說,defer 語句中的參數(shù)會(huì)在聲明 defer 時(shí)計(jì)算,而不是在 defer 執(zhí)行時(shí)計(jì)算。
三個(gè)函數(shù)組合構(gòu)成了錯(cuò)誤處理,如下:
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Starting the program")
causePanic()
fmt.Println("Program ended gracefully")
}
func causePanic() {
fmt.Println("About to cause panic")
panic("A severe error occurred")
fmt.Println("This line will not execute")
}
Starting the program About to cause panic Recovered from panic: A severe error occurred
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Golang使用Gin框架實(shí)現(xiàn)http分塊傳輸
這篇文章主要為大家詳細(xì)介紹了Golang中如何使用Gin框架實(shí)現(xiàn)http分塊傳輸功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-05-05
GoLang并發(fā)編程中條件變量sync.Cond的使用
Go標(biāo)準(zhǔn)庫提供Cond原語的目的是,為等待/通知場(chǎng)景下的并發(fā)問題提供支持,本文主要介紹了Go并發(fā)編程sync.Cond的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下2023-01-01
GO中的slice使用簡(jiǎn)介(源碼分析slice)
slice(切片)是go中常見和強(qiáng)大的類型,這篇文章不是slice使用簡(jiǎn)介,從源碼角度來分析slice的實(shí)現(xiàn),slice的一些迷惑的使用方式,感興趣的朋友跟隨小編一起看看吧2023-06-06
從零封裝Gin框架實(shí)現(xiàn)日志初始化及切割歸檔功能
這篇文章主要為大家介紹了從零封裝Gin框架實(shí)現(xiàn)日志初始化及切割歸檔功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
go使用支付寶沙箱實(shí)現(xiàn)支付寶支付的操作步驟
支付寶沙箱支付是支付寶提供的一個(gè)測(cè)試環(huán)境,用于開發(fā)者在不影響真實(shí)交易的情況下進(jìn)行支付接口的開發(fā)和調(diào)試,本文給大家介紹了go使用支付寶沙箱實(shí)現(xiàn)支付寶支付的操作步驟,文中有詳細(xì)的代碼示例和圖文供大家參考,需要的朋友可以參考下2024-03-03
Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解
這篇文章主要介紹了Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解,需要的朋友可以參考下2022-04-04
Golang根據(jù)job數(shù)量動(dòng)態(tài)控制每秒?yún)f(xié)程的最大創(chuàng)建數(shù)量方法詳解
這篇文章主要介紹了Golang根據(jù)job數(shù)量動(dòng)態(tài)控制每秒?yún)f(xié)程的最大創(chuàng)建數(shù)量方法2024-01-01
go語言日志記錄庫簡(jiǎn)單使用方法實(shí)例分析
這篇文章主要介紹了go語言日志記錄庫簡(jiǎn)單使用方法,實(shí)例分析了Go語言日志記錄的操作的技巧,需要的朋友可以參考下2015-03-03

