Golang中的錯(cuò)誤處理的示例詳解
1、panic
當(dāng)我們執(zhí)行panic的時(shí)候會(huì)結(jié)束下面的流程:
package main
import "fmt"
func main() {
fmt.Println("hello")
panic("stop")
fmt.Println("world")
}
輸出:
go run 9.go
hello
panic: stop
但是panic也是可以捕獲的,我們可以使用defer和recover實(shí)現(xiàn):
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
那什么時(shí)候適合panic呢?在 Go 中,panic 用于表示真正的異常,例如程序錯(cuò)誤。我們經(jīng)常會(huì)在一些內(nèi)置包里面看到panic的身影。
比如strings.Repeat重復(fù)返回一個(gè)由字符串 s 的計(jì)數(shù)副本組成的新字符串:
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")
}
...
}
我們可以看到當(dāng)重復(fù)的次數(shù)小于0或者重復(fù)count次之后s的長(zhǎng)度溢出,程序會(huì)直接panic,而不是返回錯(cuò)誤。這時(shí)因?yàn)閟trings包限制了error的使用,所以在程序錯(cuò)誤時(shí)會(huì)直接panic。
還有一個(gè)例子是關(guān)于正則表達(dá)式的例子:
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)
}
- 編寫(xiě)一個(gè)正則表達(dá)式
- 調(diào)用Compile,解析正則表達(dá)式,如果成功,返回用于匹配文本的 Regexp 對(duì)象。否則返回錯(cuò)誤
- 利用正則,在輸入的字符串中,獲取所有的匹配字符
可以看到如果上面正則解析失敗是可以繼續(xù)往下執(zhí)行的,但是regexp包中還有另外一個(gè)方法MustCompile:
func MustCompile(str string) *Regexp {
regexp, err := Compile(str)
if err != nil {
panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
}
return regexp
}
這個(gè)方法說(shuō)明正則的解析是強(qiáng)依賴(lài)的,如果解析錯(cuò)誤,直接panic結(jié)束程序。用戶(hù)可以根據(jù)實(shí)際情況選擇。
但是實(shí)際開(kāi)發(fā)中我們還是要謹(jǐn)慎使用panic,因?yàn)樗鼤?huì)使程序結(jié)束運(yùn)行(除非我們調(diào)用defer recover)
2、包裝錯(cuò)誤
錯(cuò)誤包裝是將錯(cuò)誤包裝或者打包在一個(gè)包裝容器中,這樣的話我們就可以追溯到源錯(cuò)誤。錯(cuò)誤包裝的主要作用就是:
- 為錯(cuò)誤添加上下文
- 將錯(cuò)誤標(biāo)記為特定類(lèi)型的錯(cuò)誤
我們可以看一個(gè)訪問(wèn)數(shù)據(jù)庫(kù)的例子:
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, "六月的想訪問(wèn)這個(gè)課件") // 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)
}
}
- 訪問(wèn)數(shù)據(jù)庫(kù)時(shí)我們返回了原始的錯(cuò)誤信息
- 到上層我們添加了一些自定義的上下文信息
輸出:
go run 9.go
六月的想訪問(wèn)這個(gè)課件: permission denied
當(dāng)然我們也可以將錯(cuò)誤包裝成我們自定義類(lèi)型的錯(cuò)誤,我們稍微修改下上面的例子:
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)
}
}
- 首先我們自定義了ForbiddenError的錯(cuò)誤類(lèi)型
- 我們實(shí)現(xiàn)了error接口
- 訪問(wèn)數(shù)據(jù)庫(kù)拋出原始錯(cuò)誤
- 上層返回ForbiddenError類(lèi)型的錯(cuò)誤
輸出:
go run 9.go
Forbidden: permission denied
當(dāng)然我們也可以不用創(chuàng)建自定義錯(cuò)誤的類(lèi)型,去包裝錯(cuò)誤添加上下文:
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包裝錯(cuò)誤
使用這的好處是我們可以追溯到源錯(cuò)誤,從而方便我們做一些特殊的處理。
還有一種方式是使用:
return nil, fmt.Errorf("another wrap err: %v", err)
%v的方式不會(huì)包裝錯(cuò)誤,所以無(wú)法追溯到源錯(cuò)誤,但往往有時(shí)候我們會(huì)選擇這種方式,而不用%w的方式。%w的方式雖然能包裝源錯(cuò)誤,但往往我們會(huì)通過(guò)源錯(cuò)誤去做一些處理,假如源錯(cuò)誤被修改,那包裝這個(gè)源錯(cuò)誤的相關(guān)錯(cuò)誤都需要做響應(yīng)變化。
3、錯(cuò)誤類(lèi)型判斷
我們擴(kuò)展一下上面查詢(xún)課件的例子?,F(xiàn)在我們有這樣的判斷,如果傳進(jìn)來(lái)的id不合法我們返回400錯(cuò)誤,如果查詢(xún)數(shù)據(jù)庫(kù)報(bào)錯(cuò)我們返回500錯(cuò)誤,我們可以像下面這樣寫(xiě):
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看下打印的結(jié)構(gòu)
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
這樣看起來(lái)好像也沒(méi)什么問(wèn)題,現(xiàn)在我們稍微修改下代碼,把上面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}) // 這里包裝了一層錯(cuò)誤
}
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錯(cuò)誤進(jìn)到了400里面,這并不是我們想要的結(jié)果。之所以會(huì)這樣,是因?yàn)樵贔orbiddenError的外面又包裝了一層Error錯(cuò)誤,使用類(lèi)型斷言的時(shí)候判斷出來(lái)的是Error錯(cuò)誤,所以進(jìn)到了400分支。
這里我們可以使用errors.As方法,它會(huì)遞歸調(diào)用Unwrap方法,找到錯(cuò)誤鏈中第一個(gè)與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 // 這里實(shí)現(xiàn)了*ForbiddenError接口,不然會(huì)panic
if errors.As(err, &f) { // 找到匹配的錯(cuò)誤
fmt.Println("500 err: ", err)
} else {
fmt.Println("400 err: ", err)
}
}
}
輸出:
go run 9.go
500 err: wrap err: Forbidden: permission denied
4、錯(cuò)誤值判斷
在代碼中或者mysql庫(kù)或者io庫(kù)中我們經(jīng)常會(huì)看到這樣的全局錯(cuò)誤:
var ErrCourseware = errors.New("courseware")
這種錯(cuò)誤我們稱(chēng)之為哨兵錯(cuò)誤。一般數(shù)據(jù)庫(kù)沒(méi)查到ErrNoRows或者io讀到了EOF錯(cuò)誤,這些特定的錯(cuò)誤可以幫助我們做一些特殊的處理。
一般我們會(huì)直接用==號(hào)判斷錯(cuò)誤值,但是就像上面的如果錯(cuò)誤被包裝哪我們就不好去判斷了。好在errors包中提供了errors.Is方法,通過(guò)遞歸調(diào)用Unwrap判斷錯(cuò)誤鏈中是否與目標(biāo)錯(cuò)誤相匹配的錯(cuò)誤值:
if err != nil {
if errors.Is(err, ErrCourseware) {
// ...
} else {
// ...
}
}到此這篇關(guān)于Golang中的錯(cuò)誤處理的示例詳解的文章就介紹到這了,更多相關(guān)Golang錯(cuò)誤處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解golang函數(shù)多返回值錯(cuò)誤處理與error類(lèi)型
- Go語(yǔ)言中的錯(cuò)誤處理最佳實(shí)踐詳解
- 重學(xué)Go語(yǔ)言之錯(cuò)誤處理與異常機(jī)制詳解
- Go語(yǔ)言中錯(cuò)誤處理的方式總結(jié)
- Go語(yǔ)言中實(shí)現(xiàn)完美錯(cuò)誤處理實(shí)踐分享
- Golang錯(cuò)誤處理方式異常與error
- Golang中的錯(cuò)誤處理深入分析
- Golang中的錯(cuò)誤處理的示例詳解
- Go 代碼規(guī)范錯(cuò)誤處理示例經(jīng)驗(yàn)總結(jié)
- Go?錯(cuò)誤處理實(shí)踐總結(jié)示例
- Go錯(cuò)誤處理的幾種方式
相關(guān)文章
Go調(diào)度器學(xué)習(xí)之協(xié)作與搶占詳解
如果某個(gè)G執(zhí)行時(shí)間過(guò)長(zhǎng),其他的G如何才能被正常調(diào)度,這就引出了接下來(lái)的話題:協(xié)作與搶占。本文將通過(guò)一些示例為大家詳細(xì)講講調(diào)度器中協(xié)作與搶占的相關(guān)知識(shí),需要的可以參考一下2023-04-04
深入理解golang的基本類(lèi)型排序與slice排序
大家都知道排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部的排序記錄,在排序過(guò)程中需要訪問(wèn)外存。下面就來(lái)詳細(xì)介紹golang的基本類(lèi)型排序與slice排序,有需要的朋友們可以參考借鑒。2016-09-09
Golang實(shí)現(xiàn)自定義recovery中間件
在?Golang?的?Web?項(xiàng)目中,自定義?recovery?中間件是一種常見(jiàn)的做法,用于捕獲并處理應(yīng)用程序的運(yùn)行時(shí)錯(cuò)誤,下面我們就來(lái)看看具體如何實(shí)現(xiàn)吧2023-09-09
Golang實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能
這篇文章主要為大家詳細(xì)介紹了Golang實(shí)現(xiàn)斷點(diǎn)續(xù)傳、復(fù)制文件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
Golang實(shí)現(xiàn)簡(jiǎn)易的命令行功能
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)Golang實(shí)現(xiàn)一個(gè)簡(jiǎn)易的命令行功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下2023-02-02
go語(yǔ)法入門(mén)any類(lèi)型的使用場(chǎng)景示例詳解
這篇文章主要為大家介紹了go語(yǔ)法入門(mén)any類(lèi)型的使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

