Errors & Generics
การจัดการ Error ใน Go
หัวข้อที่มีชื่อว่า “การจัดการ Error ใน Go”ภาษาส่วนใหญ่จัดการกับความล้มเหลวผ่าน 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
interface error
หัวข้อที่มีชื่อว่า “interface error”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 Values | errors.New, fmt.Errorf, sentinel errors, errors.Is |
| Wrapping & Custom Errors | %w, errors.As, custom error types |
| Panic & Recover | เมื่อไหรควร panic, recover, การ defer cleanup |
| Generics | Type 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 มองเห็นได้ชัดเจนใน code | verbose กว่า try/catch |
| error as value (not exception) | composable, predictable control flow | caller ต้องตรวจ error ทุกครั้ง |
| generics (Go 1.18+) | type-safe reuse, ไม่ต้อง interface{} | compile time เพิ่ม, syntax ซับซ้อนกว่า |
| panic/recover | abort ทันทีเมื่อ 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