跳至主要内容

[Golang] 錯誤處理 error handling

此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料:

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
}

參考