[Golang] 錯誤處理 error handling
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料:
- Error handling and Go @ Go Blog
- package errors @ pkg.go
TL;DR
import "errors"
err := errors.New("建立錯誤訊息")
err := fmt.Errorf("user %s had an error: %s", user, msg)
err.Error() // 取得錯誤訊息
在 golang 中 error
型態底層是 interface,這個 error 裡面有 Error()
這個 function,並且會回傳 string。
Error Interface
error
是 golang 內建的 interface,和fmt.Stringer
很類似
type error interface {
Error() string
}
顯示錯誤訊息
err.Error()
fmt.Errorf("username %s is already existed", username)
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
}
產生錯誤訊息
作法:使用 errors.New()
直接回傳 error
的型別,並搭配 errors
package 提供的 errors.New()
來產生錯誤訊息:
import (
"fmt"
"errors"
)
func checkUserNameExist(username string) (bool, error) {
if username == "bar" {
return true, errors.New("username bar is already exist")
}
return false, nil
}
func main() {
if _, err := checkUserNameExist("bar"); err != nil {
fmt.Println(err)
}
}
作法:使用 fmt.Errorf()
直接回傳 error
的型別,並搭配 fmt.Errorf()
來產生錯誤訊息:
import "fmt"
// 回傳的型別中使用 error
func checkUserNameExist(username string) (bool, error) {
if username == "foo" {
// 搭配 fmt.Errorf 來產生錯誤訊息
return true, fmt.Errorf("username %s is already existed", username)
}
return false, nil
}
func main() {
if _, err := checkUserNameExist("foo"); err != nil {
fmt.Println(err) // username foo is already existed
}
}
作法:自己定義 error structure(一)
由於 Error 的 interface 是:
type error interface {
Error() string
}
開發者便可以利用這個 interface 客製化自己的錯誤訊息。
// A Tour of Go,https://tour.golang.org/methods/19
// STEP 1:定義客製化的 error struct
type MyError struct {
When time.Time
What string
}
// STEP 2:定義能夠屬於 Error Interface 的方法
func (e MyError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
// STEP 3:拋出錯誤的函式
func run() error {
return MyError{
When: time.Now(),
What: "id didn't work",
}
}
// STEP 4:使用 fmt.Println 即可取得錯誤拋出的訊息
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
另外,也可以使用 err.(ErrorStruct)
來判斷該 Error 是否屬於自己定義的錯誤型態:
errorStruct, isErrorStruct := err.(ErrorStruct)
實作上來說,會像這樣:
type MyError struct {
When time.Time
What string
}
func (e MyError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
return MyError{
When: time.Now(),
What: "id didn't work",
}
}
// STEP 1:定義另一個 Error struct
type NotMyError struct{}
func (f NotMyError) Error() string {
return "foo"
}
func main() {
if err := run(); err != nil {
var isMyError bool
// STEP 2:使用 err.(ErrorStruct) 來判斷該錯誤使否屬於該 Error Type
_, isMyError = err.(NotMyError) // 判斷該錯誤是否屬於 NotMyError Type
fmt.Println("isMyError", isMyError) // false
_, isMyError = err.(MyError) // 判斷該錯誤是否屬於 MyError Type
fmt.Println("isMyError", isMyError) // true
}
}
作法:自己定義 error structure(二)
package main
import "fmt"
// STEP 1:定義一個 error type
type errUserNameExist struct {
UserName string
}
// STEP 2:擴充 Error
func (e errUserNameExist) Error() string {
return fmt.Sprintf("username %s is already existed", e.UserName)
}
func checkUserNameExist(username string) (bool, error) {
if username == "foo" {
// STEP 3:使用定義好的 error struct
return true, errUserNameExist{
UserName: username,
}
}
return false, nil
}
func main() {
if _, err := checkUserNameExist("foo"); err != nil {
fmt.Println(err) // username foo is already existed
}
}
也可以使用 err.(ErrorType)
進一步判斷這個 error type 是不是自己定義的 error structure(errUserNameExist
):
type errUserNameExist struct {
UserName string
}
func (e errUserNameExist) Error() string {
return fmt.Sprintf("username %s is already existed", e.UserName)
}
// STEP 1:判斷這個 err 是不是自己定義的 errUserNameExist
func isErrUserNameExist(err error) bool {
// 因為 err 是 interface,裡面可以定義各種不同的形態
_, ok := err.(errUserNameExist)
return ok
}
func checkUserNameExist(username string) (bool, error) {
if username == "bar" {
return true, errors.New("bar exist")
}
if username == "foo" {
return true, errUserNameExist{
UserName: username,
}
}
return false, nil
}
func main() {
if _, err := checkUserNameExist("foo"); err != nil {
// STEP 2:進行判斷
if isErrUserNameExist(err) {
fmt.Println(err) // username foo is already existed
}
}
if _, err := checkUserNameExist("bar"); err != nil {
if isErrUserNameExist(err) {
fmt.Println(err) // <empty>
} else {
fmt.Println(err) // bar exist
}
}
}
錯誤發生時終止程式執行
若有需要在錯誤發生時終止程式執行,可以使用 os
提供的 os.Exit()
方法:
if err != nil {
fmt.Println("Error: ", err)
os.Exit(1) // 終止程式繼續執行
}
判斷錯誤類型
使用 err.(*T)
的方式可以用來判斷錯誤的型別,例如 errMsg, isUnmarshalError := err.(*json.UnmarshalTypeError)
:
type SensorReading struct {
Name string `json:"name"`
Capacity int `json:"capacity"`
}
func main() {
jsonString := `{"name": "battery sensor", "capacity": "wrong time"}`
reading := SensorReading{}
err := json.Unmarshal([]byte(jsonString), &reading)
if err != nil {
unmarshalErr, isUnmarshalError := err.(*json.UnmarshalTypeError) // 使用 err.(T) 來判斷錯誤的型別
fmt.Println(unmarshalErr) // json: cannot unmarshal string into Go struct field SensorReading.capacity of type int
fmt.Println(isUnmarshalError) // true
}
fmt.Printf("%+v\n", reading) // {Name:battery sensor Capacity:0}
}
也可以用來判斷客制的錯誤類型:
if pccErrors, ok := err.(*pcc.ErrorResp); ok {
firstError := pccErrors.Errors[0]
statusCode, err := strconv.Atoi(firstError.Status)
if err != nil {
log.Warn("[api/helper] SuccessOrAbort - strconv.Atoi failed", err)
}
_ = ctx.AbortWithError(statusCode, fmt.Errorf("%s:%s", firstError.Title, firstError.Detail))
return
}
參考
- Go 語言基礎實戰 (開發, 測試及部署) @ Udemy by Bo-Yi Wu
- Go 語言的錯誤訊息處理 by appleboy
- Go (Golang): The Complete Bootcamp @ Udemy by Jose Portilla
- Go: The Complete Developer's Guide (Golang) @ Udemy by Stephen Grider