一、Go 中的 error 是什麼?
在 Go 中,error 是一個內置接口類型,定義如下:
type error interface {
Error() string
}
只要一個類型實現了 Error() string 方法,它就是一個 error。
最常用的創建錯誤的方式是使用標準庫 errors.New 或 fmt.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.Is 或 errors.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 還提供了 panic 和 recover 機制,但不應用於常規錯誤處理,僅適用於程序無法繼續運行的嚴重錯誤(如數組越界、空指針解引用等)。
func riskyFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕獲到 panic:", r)
}
}()
panic("發生嚴重錯誤!")
}
⚠️ 官方建議:優先使用 error 返回值,而不是 panic。panic 應該是“最後的手段”。
六、錯誤處理的最佳實踐
- 不要忽略 error:即使暫時不處理,也應記錄日誌。
- 儘早返回錯誤:避免深層嵌套
if。
if err != nil {
return err
}
// 繼續主邏輯
- 提供有意義的錯誤信息:幫助調用者快速定位問題。
- 使用錯誤包裝傳遞上下文:讓錯誤鏈更清晰。
- 在包的邊界統一處理錯誤:例如 Web 服務中,在 HTTP handler 層統一返回 JSON 錯誤。