ข้ามไปยังเนื้อหา

Errors & Generics

ภาษาส่วนใหญ่จัดการกับความล้มเหลวผ่าน exceptions — กลไกพิเศษที่ควบคุมการไหลของโปรแกรมโดยการ unwind call stack จนกว่า catch block จะดักจับปัญหาไว้ได้ Go เลือกแนวทางที่แตกต่างอย่างสิ้นเชิง: errors คือค่าธรรมดา

ฟังก์ชันที่อาจล้มเหลวจะคืนค่าสองค่า: ผลลัพธ์และ error caller รับทั้งคู่และตัดสินใจว่าจะทำอะไร ไม่มี hidden control flow ไม่มี stack unwinding ไม่มี unchecked exceptions ทุก callsite ที่อาจเกิด error มองเห็นได้ในโค้ด ทำให้โปรแกรมตรวจสอบและทำความเข้าใจได้ง่ายขึ้น

รูปแบบ canonical ที่คุณจะเห็นทุกที่ใน Go:

result, err := doSomething()
if err != nil {
// จัดการความล้มเหลว — return, log, wrap, หรือ recover
}
// err เป็น nil: ใช้ result ได้อย่างปลอดภัย

nil หมายถึงสำเร็จ error ที่ไม่ใช่ nil หมายถึงมีบางอย่างผิดพลาดและไม่ควรเชื่อถือ result

type error ของ Go นิยามไว้เป็น interface ที่มีเพียงหนึ่ง method ใน standard library:

type error interface {
Error() string
}

type ใดก็ตามที่ implement Error() string จะ satisfy interface error ได้ ซึ่งหมายความว่าคุณสามารถส่งผ่าน error แบบ string ธรรมดา, error ที่มีโครงสร้างซับซ้อน และทุกอย่างที่อยู่ระหว่างนั้น — ทั้งหมดผ่าน interface เดียวกัน nil เป็นค่า error ที่ถูกต้องและหมายถึงไม่มี error ใด ๆ เกิดขึ้น

บทเรียนหัวข้อ
หน้านี้ภาพรวมแนวคิดและตัวอย่างแรกที่รันได้
Error Valueserrors.New, fmt.Errorf, sentinel errors, errors.Is
Wrapping & Custom Errors%w, errors.As, custom error types
Panic & Recoverเมื่อไหรควร panic, recover, การ defer cleanup
GenericsType parameters, constraints, ~T, union types

ฟังก์ชันเล็ก ๆ ที่คืนค่า (string, error) เพื่อแสดงรูปแบบพื้นฐานของการจัดการ error ใน Go:

package main
import (
"errors"
"fmt"
)
func greet(name string) (string, error) {
if name == "" {
return "", errors.New("name must not be empty")
}
return fmt.Sprintf("Hello, %s!", name), nil
}
func main() {
msg, err := greet("Gopher")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(msg) // Hello, Gopher!
_, err = greet("")
if err != nil {
fmt.Println("error:", err) // error: name must not be empty
}
}

errors.New สร้าง error value ธรรมดาจาก string คงที่ ฟังก์ชันคืนค่า nil เป็น error ในเส้นทางปกติ และคืนค่า error ที่ไม่ใช่ nil ในเส้นทางที่ล้มเหลว caller ตรวจสอบ if err != nil ทันทีหลังการเรียก — นี่คือ idiom ที่คุณจะเขียนนับร้อยครั้งใน Go

สิ่งที่ได้ประโยชน์ต้นทุน
explicit error handlingทุก failure point มองเห็นได้ชัดเจนใน codeverbose กว่า try/catch
error as value (not exception)composable, predictable control flowcaller ต้องตรวจ error ทุกครั้ง
generics (Go 1.18+)type-safe reuse, ไม่ต้อง interface{}compile time เพิ่ม, syntax ซับซ้อนกว่า
panic/recoverabort ทันทีเมื่อ invariant ถูกละเมิดไม่ใช่ error handling ทั่วไป
  • error handling ใน Go verbose เกินไป — verbosity นี้ตั้งใจ: ทุก failure path มองเห็นได้ในโค้ด ไม่มี hidden exception
  • panic = exception ที่ควรใช้แทน return error — panic ใช้สำหรับ programmer error เท่านั้น ไม่ใช่ expected failure
  • generics ทำ code ช้าลง — Go generics ใช้ GC-shape stenciling — overhead น้อยมากสำหรับ pointer types

💡 ตัวอย่างจากของจริง

Kubernetes ใช้ Go’s error model ทั่วทั้ง codebase — ทุก function ที่อาจล้มเหลวคืน (T, error) อย่างชัดเจน

Docker และ etcd ใช้ error wrapping เพื่อให้ log มี context ครบทุก layer ของ call stack

ใน Go เมื่อฟังก์ชันคืนค่า error ที่ไม่ใช่ nil หมายความว่าอะไร?
interface error ในตัว (built-in) นิยามว่าอย่างไร?
package ใดที่ให้ errors.New และ errors.Is?
Generics ถูกเพิ่มเข้ามาใน Go เวอร์ชันใด?