<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
當我們執行panic的時候會結束下面的流程:
package main import "fmt" func main() { fmt.Println("hello") panic("stop") fmt.Println("world") }
輸出:
go run 9.go
hello
panic: stop
但是panic也是可以捕獲的,我們可以使用defer和recover實現:
package main import "fmt" func main() { defer func() { if r := recover(); r != nil { fmt.Println("recover: ", r) } }() fmt.Println("hello") panic("stop") fmt.Println("world") }
輸出:
go run 9.go
hello
recover: stop
那什麼時候適合panic呢?在 Go 中,panic 用於表示真正的異常,例如程式錯誤。我們經常會在一些內建包裡面看到panic的身影。
比如strings.Repeat重複返回一個由字串 s 的計數副本組成的新字串:
func Repeat(s string, count int) string { if count == 0 { return "" } // if count < 0 { panic("strings: negative Repeat count") } else if len(s)*count/count != len(s) { panic("strings: Repeat count causes overflow") } ... }
我們可以看到當重複的次數小於0或者重複count次之後s的長度溢位,程式會直接panic,而不是返回錯誤。這時因為strings包限制了error的使用,所以在程式錯誤時會直接panic。
還有一個例子是關於正規表示式的例子:
package main import ( "fmt" "regexp" ) func main() { pattern := "a[a-z]b*" // 1 compile, err := regexp.Compile(pattern) // 2 if err != nil { // 2 fmt.Println("compile err: ", err) return } // 3 allString := compile.FindAllString("acbcdadb", 3) fmt.Println(allString) }
可以看到如果上面正則解析失敗是可以繼續往下執行的,但是regexp包中還有另外一個方法MustCompile:
func MustCompile(str string) *Regexp { regexp, err := Compile(str) if err != nil { panic(`regexp: Compile(` + quote(str) + `): ` + err.Error()) } return regexp }
這個方法說明正則的解析是強依賴的,如果解析錯誤,直接panic結束程式。使用者可以根據實際情況選擇。
但是實際開發中我們還是要謹慎使用panic,因為它會使程式結束執行(除非我們呼叫defer recover)
錯誤包裝是將錯誤包裝或者打包在一個包裝容器中,這樣的話我們就可以追溯到源錯誤。錯誤包裝的主要作用就是:
我們可以看一個存取資料庫的例子:
package main import ( "fmt" "github.com/pkg/errors" ) type Courseware struct { Id int64 Code string Name string } func getCourseware(id int64) (*Courseware, error) { courseware, err := getFromDB(id) if err != nil { return nil, errors.Wrap(err, "六月的想存取這個課件") // 2 } return courseware, nil } func getFromDB(id int64) (*Courseware, error) { return nil, errors.New("permission denied") // 1 } func main() { _, err := getCourseware(11) if err != nil { fmt.Println(err) } }
輸出:
go run 9.go
六月的想存取這個課件: permission denied
當然我們也可以將錯誤包裝成我們自定義型別的錯誤,我們稍微修改下上面的例子:
package main import ( "fmt" "github.com/pkg/errors" ) type Courseware struct { Id int64 Code string Name string } // 1 type ForbiddenError struct { Err error } // 2 func (e *ForbiddenError) Error() string { return "Forbidden: " + e.Err.Error() } func getCourseware(id int64) (*Courseware, error) { courseware, err := getFromDB(id) if err != nil { return nil, &ForbiddenError{err} // 4 } return courseware, nil } func getFromDB(id int64) (*Courseware, error) { return nil, errors.New("permission denied") // 3 } func main() { _, err := getCourseware(11) if err != nil { fmt.Println(err) } }
輸出:
go run 9.go
Forbidden: permission denied
當然我們也可以不用建立自定義錯誤的型別,去包裝錯誤新增上下文:
package main import ( "fmt" "github.com/pkg/errors" ) type Courseware struct { Id int64 Code string Name string } func getCourseware(id int64) (*Courseware, error) { courseware, err := getFromDB(id) if err != nil { return nil, fmt.Errorf("another wrap err: %w", err) // 1 } return courseware, nil } func getFromDB(id int64) (*Courseware, error) { return nil, errors.New("permission denied") } func main() { _, err := getCourseware(11) if err != nil { fmt.Println(err) } }
使用%w包裝錯誤
使用這的好處是我們可以追溯到源錯誤,從而方便我們做一些特殊的處理。
還有一種方式是使用:
return nil, fmt.Errorf("another wrap err: %v", err)
%v的方式不會包裝錯誤,所以無法追溯到源錯誤,但往往有時候我們會選擇這種方式,而不用%w的方式。%w的方式雖然能包裝源錯誤,但往往我們會通過源錯誤去做一些處理,假如源錯誤被修改,那包裝這個源錯誤的相關錯誤都需要做響應變化。
我們擴充套件一下上面查詢課件的例子。現在我們有這樣的判斷,如果傳進來的id不合法我們返回400錯誤,如果查詢資料庫報錯我們返回500錯誤,我們可以像下面這樣寫:
package main import ( "fmt" "github.com/pkg/errors" ) type Courseware struct { Id int64 Code string Name string } type ForbiddenError struct { Err error } func (e *ForbiddenError) Error() string { return "Forbidden: " + e.Err.Error() } func getCourseware(id int64) (*Courseware, error) { if id <= 0 { return nil, fmt.Errorf("invalid id: %d", id) } courseware, err := getFromDB(id) if err != nil { return nil, &ForbiddenError{err} } return courseware, nil } func getFromDB(id int64) (*Courseware, error) { return nil, errors.New("permission denied") } func main() { _, err := getCourseware(500) // 我們可以修改這裡的id看下列印的結構 if err != nil { switch err := err.(type) { case *ForbiddenError: fmt.Println("500 err: ", err) default: fmt.Println("400 err: ", err) } } }
輸出:
go run 9.go
500 err: Forbidden: permission denied
這樣看起來好像也沒什麼問題,現在我們稍微修改下程式碼,把上面ForbiddenError包裝一下:
package main import ( "fmt" "github.com/pkg/errors" ) type Courseware struct { Id int64 Code string Name string } type ForbiddenError struct { Err error } func (e *ForbiddenError) Error() string { return "Forbidden: " + e.Err.Error() } func getCourseware(id int64) (*Courseware, error) { if id <= 0 { return nil, fmt.Errorf("invalid id: %d", id) } courseware, err := getFromDB(id) if err != nil { return nil, fmt.Errorf("wrap err: %w", &ForbiddenError{err}) // 這裡包裝了一層錯誤 } return courseware, nil } func getFromDB(id int64) (*Courseware, error) { return nil, errors.New("permission denied") } func main() { _, err := getCourseware(500) if err != nil { switch err := err.(type) { case *ForbiddenError: fmt.Println("500 err: ", err) default: fmt.Println("400 err: ", err) } } }
輸出:
go run 9.go
400 err: wrap err: Forbidden: permission denied
可以看到我們的Forbidden錯誤進到了400裡面,這並不是我們想要的結果。之所以會這樣,是因為在ForbiddenError的外面又包裝了一層Error錯誤,使用型別斷言的時候判斷出來的是Error錯誤,所以進到了400分支。
這裡我們可以使用errors.As方法,它會遞迴呼叫Unwrap方法,找到錯誤鏈中第一個與target匹配的方法:
package main import ( "fmt" "github.com/pkg/errors" ) type Courseware struct { Id int64 Code string Name string } type ForbiddenError struct { Err error } func (e *ForbiddenError) Error() string { return "Forbidden: " + e.Err.Error() } func getCourseware(id int64) (*Courseware, error) { if id <= 0 { return nil, fmt.Errorf("invalid id: %d", id) } courseware, err := getFromDB(id) if err != nil { return nil, fmt.Errorf("wrap err: %w", &ForbiddenError{err}) } return courseware, nil } func getFromDB(id int64) (*Courseware, error) { return nil, errors.New("permission denied") } func main() { _, err := getCourseware(500) if err != nil { var f *ForbiddenError // 這裡實現了*ForbiddenError介面,不然會panic if errors.As(err, &f) { // 找到匹配的錯誤 fmt.Println("500 err: ", err) } else { fmt.Println("400 err: ", err) } } }
輸出:
go run 9.go
500 err: wrap err: Forbidden: permission denied
在程式碼中或者mysql庫或者io庫中我們經常會看到這樣的全域性錯誤:
var ErrCourseware = errors.New("courseware")
這種錯誤我們稱之為哨兵錯誤。一般資料庫沒查到ErrNoRows或者io讀到了EOF錯誤,這些特定的錯誤可以幫助我們做一些特殊的處理。
一般我們會直接用==號判斷錯誤值,但是就像上面的如果錯誤被包裝哪我們就不好去判斷了。好在errors包中提供了errors.Is方法,通過遞迴呼叫Unwrap判斷錯誤鏈中是否與目標錯誤相匹配的錯誤值:
if err != nil { if errors.Is(err, ErrCourseware) { // ... } else { // ... } }
到此這篇關於Golang中的錯誤處理的範例詳解的文章就介紹到這了,更多相關Golang錯誤處理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45