一、Go 中的 error 是什麼?

在 Go 中,error 是一個內置接口類型,定義如下:

type error interface {
    Error() string
}

只要一個類型實現了 Error() string 方法,它就是一個 error

最常用的創建錯誤的方式是使用標準庫 errors.Newfmt.Errorf

import (
    "errors"
    "fmt"
)

err1 := errors.New("用户名不能為空")
err2 := fmt.Errorf("文件 %s 未找到", filename)

二、函數如何返回錯誤?

Go 鼓勵函數將 error 作為最後一個返回值。調用者必須顯式檢查該錯誤。

示例:安全除法函數

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除數不能為零")
    }
    return a / b, nil
}

// 調用
result, err := divide(10, 0)
if err != nil {
    fmt.Println("出錯了:", err)
    return
}
fmt.Println("結果是:", result)

✅ 關鍵點:永遠不要忽略 error! 即使你認為“不可能出錯”,也要檢查。


三、自定義錯誤類型

對於複雜項目,你可能需要更豐富的錯誤信息。這時可以定義自己的錯誤類型。

type DivisionError struct {
    Dividend float64
    Divisor  float64
}

func (e *DivisionError) Error() string {
    return fmt.Sprintf("無法用 %.2f 除以 %.2f", e.Dividend, e.Divisor)
}

func safeDivide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, &DivisionError{Dividend: a, Divisor: b}
    }
    return a / b, nil
}

這樣,調用方不僅能知道出錯,還能獲取具體上下文。


四、錯誤包裝與 Unwrap(Go 1.13+)

從 Go 1.13 開始,標準庫支持錯誤包裝(Error Wrapping),便於在調用鏈中保留原始錯誤信息。

使用 fmt.Errorf%w 動詞包裝錯誤:

func readFile(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("讀取配置文件失敗: %w", err)
    }
    // 處理 data...
    return nil
}

調用方可以使用 errors.Iserrors.As 判斷底層錯誤:

if err := readFile("config.txt"); err != nil {
    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("文件不存在,請檢查路徑")
    } else {
        fmt.Println("其他錯誤:", err)
    }
}
  • errors.Is(err, target):判斷是否包含特定錯誤(如 os.ErrNotExist
  • errors.As(err, &target):嘗試將錯誤轉換為特定類型

五、panic 與 recover —— 謹慎使用!

Go 還提供了 panicrecover 機制,但不應用於常規錯誤處理,僅適用於程序無法繼續運行的嚴重錯誤(如數組越界、空指針解引用等)。

func riskyFunction() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕獲到 panic:", r)
        }
    }()
    panic("發生嚴重錯誤!")
}

⚠️ 官方建議:優先使用 error 返回值,而不是 panic。panic 應該是“最後的手段”。


六、錯誤處理的最佳實踐

  1. 不要忽略 error:即使暫時不處理,也應記錄日誌。
  2. 儘早返回錯誤:避免深層嵌套 if
if err != nil {
    return err
}
// 繼續主邏輯
  1. 提供有意義的錯誤信息:幫助調用者快速定位問題。
  2. 使用錯誤包裝傳遞上下文:讓錯誤鏈更清晰。
  3. 在包的邊界統一處理錯誤:例如 Web 服務中,在 HTTP handler 層統一返回 JSON 錯誤。