網站首頁 編程語言 正文
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.cnblogs.com/liuyuede123/p/16850908.html
相關推薦
- 2022-10-24 centos編譯安裝mariadb的詳細過程_mariadb
- 2022-03-09 軟件構建工具makefile基礎講解_C 語言
- 2023-12-07 com.fasterxml.jackson.databind.ObjectMapper
- 2022-06-14 Docker?配置容器固定IP的方法_docker
- 2023-02-04 Rust?語言的全鏈路追蹤庫?tracing使用方法_Rust語言
- 2022-07-10 數組的遍歷方法有哪些
- 2022-05-28 C++實現簡單的學生成績管理系統_C 語言
- 2022-10-03 react項目升級報錯,babel報錯,.babelrc配置兼容等問題及解決_React
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支