0.1、索引
https://waterflow.link/articles/1666716727236
1、panic
當我們執行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)
}
- 撰寫一個正則運算式
- 呼叫Compile,決議正則運算式,如果成功,回傳用于匹配文本的 Regexp 物件,否則回傳錯誤
- 利用正則,在輸入的字串中,獲取所有的匹配字符
可以看到如果上面正則決議失敗是可以繼續往下執行的,但是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)
2、包裝錯誤
錯誤包裝是將錯誤包裝或者打包在一個包裝容器中,這樣的話我們就可以追溯到源錯誤,錯誤包裝的主要作用就是:
- 為錯誤添加背景關系
- 將錯誤標記為特定型別的錯誤
我們可以看一個訪問資料庫的例子:
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)
}
}
- 首先我們自定義了ForbiddenError的錯誤型別
- 我們實作了error介面
- 訪問資料庫拋出原始錯誤
- 上層回傳ForbiddenError型別的錯誤
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的方式雖然能包裝源錯誤,但往往我們會通過源錯誤去做一些處理,假如源錯誤被修改,那包裝這個源錯誤的相關錯誤都需要做回應變化,
3、錯誤型別判斷
我們擴展一下上面查詢課件的例子,現在我們有這樣的判斷,如果傳進來的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
4、錯誤值判斷
在代碼中或者mysql庫或者io庫中我們經常會看到這樣的全域錯誤:
var ErrCourseware = errors.New("courseware")
這種錯誤我們稱之為哨兵錯誤,一般資料庫沒查到ErrNoRows或者io讀到了EOF錯誤,這些特定的錯誤可以幫助我們做一些特殊的處理,
一般我們會直接用==號判斷錯誤值,但是就像上面的如果錯誤被包裝哪我們就不好去判斷了,好在errors包中提供了errors.Is方法,通過遞回呼叫Unwrap判斷錯誤鏈中是否與目標錯誤相匹配的錯誤值:
if err != nil {
if errors.Is(err, ErrCourseware) {
// ...
} else {
// ...
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/525854.html
標籤:Go
上一篇:變數、常量
下一篇:FastDFS
