iT邦幫忙

2023 iThome 鐵人賽

DAY 30
0
Modern Web

就是個Go,我也可以啦!GOGO系列 第 30

Day 30 or 30000... 繼續學習下去...今天就學 Go錯誤處理

  • 分享至 

  • xImage
  •  

錯誤處理是一個非常重要的機制,我們都希望出錯時會 “引發” 錯誤,可以讓我們即時知道,並跳出程式,但go不同,go語言設計者選擇了C語言家族的經典錯誤機制:

錯誤就是值

然而,要把Go寫得好,就要時刻想者錯誤處理,接下來我們就來看這最後一部分吧

建構錯誤值

錯誤(errors)是作為值(values)被處理的
Go 語言中的 error 類型
error 是 Go 語言中的一個內建介面。它定義了一個方法 Error() string,這個方法返回錯誤的描述

type error interface {
    Error() string
}

處理錯誤的慣例

在 Go 中,通常我們會將錯誤作為函數的最後一個返回值返回。如果函數操作成功,則返回的錯誤值為 nil;如果出現錯誤,則返回的錯誤值包含錯誤的詳細信息。這樣,我們可以通過檢查錯誤值是否為 nil 來判斷操作是否成功

func doSomething(param1 string) (string, error) {
    // ... 做一些事情 ...

    // 如果一切正常
    return result, nil

    // 如果發生錯誤
    // return "", errors.New("描述錯誤的信息")
}

在調用函數或方法時,我們通常會檢查返回的錯誤值:

result, err := doSomething("paramValue")
if err != nil {
    // 處理錯誤
    log.Println("發生錯誤:", err)
    return
}
// 繼續正常處理

透明的處理策略

最簡單的處理策略莫過於完全不關心返回錯誤值帶回的帶回的上下文

err := doSomething()
if err != nil {
  // 完全不關心err帶來的上下文
  return err
}

這種策略在當我們不需要關心錯誤的具體信息或者不打算在當前函數中解決這個錯誤時特別有用。我們只需將錯誤傳播上去,讓調用者決定如何處理它

這種策略有時也可能導致錯誤上下文的丟失。如果下層函數的錯誤信息不足以讓上層調用者理解問題的所在,那麼可能需要加一些上下文信息來幫助調試

err := doSomething()
if err != nil {
  // 為錯誤添加一些上下文信息
  return fmt.Errorf("doing something: %w", err)
}

哨兵錯誤處理策略

當使用哨兵錯誤策略來定義不同的錯誤類型時,通常我們會在包(package)層面定義一些公共的錯誤值。這些錯誤值通常以 Err 為前綴,以便快速識別它們是錯誤值。這樣做有助於使用者理解和預期可能的錯誤情況,並能夠基於不同的錯誤進行不同的處理

package mypackage

import "errors"

var (
    ErrNotFound       = errors.New("item not found")
    ErrPermissionDenied = errors.New("permission denied")
    ErrInvalidInput   = errors.New("invalid input")
    // 可以添加更多的錯誤類型...
)

於是我們就可以這樣做

func HandleError(err error) {
    switch err {
    case mypackage.ErrNotFound:
        fmt.Println("Handle item not found...")
        // 特定的錯誤處理程式碼
    
    case mypackage.ErrPermissionDenied:
        fmt.Println("Handle permission denied...")
        // 特定的錯誤處理程式碼
        
    case mypackage.ErrInvalidInput:
        fmt.Println("Handle invalid input...")
        // 特定的錯誤處理程式碼
    
    default:
        fmt.Println("Handle generic error...")
        // 通用錯誤處理程式碼
    }
}

缺點:

  • 必須預定義這些錯誤值,不夠靈活
  • 耦合性高

錯誤值類型檢視策略

這種策略通常涉及到定義自定義錯誤類型,並在錯誤發生時返回這些類型的實例,再通過類型斷言來檢查和處理它們

定義自定義錯誤類型

你可以定義一個或多個錯誤類型來表示你的函數或方法可能返回的不同錯誤情況。這些類型通常會實現 error 接口

type NotFoundError struct {
    ItemID string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("Item with ID %s not found", e.ItemID)
}

type PermissionDeniedError struct {
    UserID string
}

func (e *PermissionDeniedError) Error() string {
    return fmt.Sprintf("User with ID %s does not have permission", e.UserID)
}

NotFoundError 類型實現了 error 介面,你可以在錯誤發生時創建這個類型的實例並將其作為一個錯誤對象返回

通過類型斷言處理特定的錯誤類型:

item, err := FindItem("123")
if err != nil {
    if e, ok := err.(*NotFoundError); ok {
        // 在這裡 e 是一個指向 NotFoundError 的指針,你可以訪問 e.ItemID 獲取更多信息
        fmt.Printf("Detailed error: Item not found, ID=%s\n", e.ItemID)
    } else {
        fmt.Println("General error:", err)
    }
    return
}

err.(*NotFoundError) 是一個類型斷言。它檢查 err 是否持有一個 *NotFoundError 的實例。如果是,e 將包含該實例並且 ok 將為 true;否則 e 將為 nil 且 ok 將為 false

這邊介紹了三個錯誤處理策略,雖然 80% 處理錯誤的慣例都會是透明的處理策略,但多認識幾個也不錯

30天的認識Go系列到這邊結束了,謝謝各位讀者願意閱讀小弟的文章,也希望可以帶給你們一點價值

我是踢一滴,謝謝各位,下台一鞠躬


上一篇
2023鐵人賽Day 29 Go X 來寫測試吧
系列文
就是個Go,我也可以啦!GOGO30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言