การจัดการ Error
Error เป็น value ไม่ใช่ exception
หัวข้อที่มีชื่อว่า “Error เป็น value ไม่ใช่ exception”TypeScript (และ JavaScript) ใช้ throw/try/catch จัดการ error — exception จะไหลขึ้นไปตาม call stack แบบมองไม่เห็น ส่วน Go เลือกทางที่ต่างออกไป: error เป็นเพียง value ฟังก์ชันที่อาจล้มเหลวจะคืน error เป็นค่าสุดท้าย แล้วผู้เรียกตัดสินใจได้ทันทีว่าจะจัดการยังไงต่อ
ตอนแรกอาจรู้สึกว่าเยิ่นเย้อ แต่พอใช้ไปสักพักคุณจะชอบที่ทุกเส้นทางที่อาจล้มเหลวนั้นชัดเจนและตามรอยได้
รูปแบบพื้นฐาน
หัวข้อที่มีชื่อว่า “รูปแบบพื้นฐาน”// TypeScript: exceptionsasync function readConfig(path: string): Promise<Config> { try { const data = await fs.readFile(path, "utf-8"); return JSON.parse(data); } catch (err) { throw new Error("failed to read config: " + err); }}// Go: errors as return valuesfunc readConfig(path string) (Config, error) { data, err := os.ReadFile(path) if err != nil { return Config{}, fmt.Errorf("failed to read config: %w", err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { return Config{}, fmt.Errorf("failed to parse config: %w", err) } return cfg, nil}การสร้าง error
หัวข้อที่มีชื่อว่า “การสร้าง error”// TypeScriptthrow new Error("something went wrong");
class NotFoundError extends Error { constructor(public id: string) { super("item " + id + " not found"); }}// Goimport "errors"import "fmt"
err := errors.New("something went wrong")
// Sentinel error (comparable with errors.Is)var ErrNotFound = errors.New("not found")
// Custom error typetype NotFoundError struct{ ID string }func (e *NotFoundError) Error() string { return fmt.Sprintf("item %s not found", e.ID)}การ wrap และ unwrap ด้วย %w
หัวข้อที่มีชื่อว่า “การ wrap และ unwrap ด้วย %w”package main
import ( "errors" "fmt")
var ErrNotFound = errors.New("not found")
type ValidationError struct { Field string Message string}
func (e *ValidationError) Error() string { return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)}
func findUser(id int) error { if id <= 0 { return &ValidationError{Field: "id", Message: "must be positive"} } if id > 100 { return fmt.Errorf("findUser: %w", ErrNotFound) } return nil}
func main() { // errors.Is unwraps the chain err := findUser(999) if errors.Is(err, ErrNotFound) { fmt.Println("user not found (sentinel matched)") }
// errors.As extracts the concrete type err = findUser(-1) var ve *ValidationError if errors.As(err, &ve) { fmt.Printf("bad field: %s — %s\n", ve.Field, ve.Message) }}Compiling on go.dev (Go 1.27)… offline? an in-browser runtime (~8 MB, Go 1.26) is used instead
errors.AsType — errors.As แบบ generic (Go 1.26+)
หัวข้อที่มีชื่อว่า “errors.AsType — errors.As แบบ generic (Go 1.26+)”errors.As ต้องประกาศตัวแปร pointer ไว้ก่อนแล้วค่อยตรวจตอน runtime ตั้งแต่ Go 1.26 errors.AsType[T] คืนค่าที่มี type ถูกต้องให้เลย — ใกล้เคียง instanceof narrowing ของ TypeScript ที่สุดเท่าที่ Go มี:
// TypeScript: instanceof narrows the type in placetry { await findUser(-1);} catch (err) { if (err instanceof ValidationError) { console.log(err.field); // narrowed to ValidationError }}// Go 1.26+: errors.AsType returns (T, bool) — no var, no pointer-to-pointerif ve, ok := errors.AsType[*ValidationError](err); ok { fmt.Println(ve.Field) // ve is already *ValidationError}
// The pre-1.26 form still works and you will see it everywhere:var ve *ValidationErrorif errors.As(err, &ve) { ... }ทั้งสองแบบไล่ chain ที่ wrap ไว้เหมือนกัน แต่ AsType เช็ค type ตอน compile (ส่ง pointer ผิดชนิดไม่ได้) และเร็วกว่าเล็กน้อย โค้ดใหม่ควรใช้ AsType ส่วน errors.As คงไว้เพื่อความเข้ากันได้