0Pricing
Go Academy · บทเรียน

การห่อและคลายข้อผิดพลาด

ใช้ %w, errors.Is และ errors.As

การห่อและคลายข้อผิดพลาด เป็นบทเรียน Go Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Go Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Go Academy มีบทเรียนทั้งหมด 4 บทเรียน

การห่อข้อผิดพลาดคืออะไร

การห่อข้อผิดพลาด คือการฝังข้อผิดพลาดเดิมไว้ภายในข้อผิดพลาดใหม่ โดยเก็บข้อผิดพลาดเดิมไว้ให้โปรแกรมตรวจสอบได้ พร้อมเพิ่มบริบทเข้าไป แนวทางนี้เริ่มใช้อย่างเหมาะสมใน Go 1.13:

package main
import ("fmt"; "errors")

var ErrDB = errors.New("database unreachable")

func queryUsers() error {
    return fmt.Errorf("queryUsers: %w", ErrDB)
}

func loadProfile(id int) error {
    return fmt.Errorf("loadProfile(%d): %w", id, queryUsers())
}

func main() {
    err := loadProfile(42)
    fmt.Println(err) // loadProfile(42): queryUsers: database unreachable
}

ตัวดำเนินการ %w

ใช้ %w (ไม่ใช่ %v) ใน fmt.Errorf เพื่อห่อข้อผิดพลาด ข้อผิดพลาดที่ถูกห่อจะเข้าถึงได้ผ่าน errors.Unwrap:

package main
import ("fmt"; "errors")

func main() {
    inner := errors.New("inner error")
    outer := fmt.Errorf("outer: %w", inner)

    fmt.Println(outer)              // outer: inner error
    fmt.Println(errors.Unwrap(outer)) // inner error
    fmt.Println(errors.Is(outer, inner)) // true
}

errors.Unwrap

errors.Unwrap คืนค่าข้อผิดพลาดถัดไปในสายโซ่ (ข้อผิดพลาดที่ถูกห่อ) หรือคืนค่า nil หากไม่มี:

package main
import ("fmt"; "errors")

type Layer struct{ msg string; err error }
func (l *Layer) Error() string { return l.msg }
func (l *Layer) Unwrap() error { return l.err }

func main() {
    root := errors.New("root")
    mid  := &Layer{"mid", root}
    top  := &Layer{"top", mid}

    fmt.Println(errors.Unwrap(top))  // mid
    fmt.Println(errors.Unwrap(mid))  // root
    fmt.Println(errors.Unwrap(root)) // <nil>
}

errors.Is — การเดินตามสายโซ่

errors.Is(err, target) จะแกะสายโซ่ข้อผิดพลาดแบบวนซ้ำจนกว่าจะพบค่าที่ตรงกันหรือถึงค่า nil:

package main
import ("fmt"; "errors")

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
    return fmt.Errorf("findItem(%d): %w",
        id, fmt.Errorf("repo: %w", ErrNotFound))
}

func main() {
    err := findItem(7)
    fmt.Println(errors.Is(err, ErrNotFound)) // true — deep in chain
    fmt.Println(err)                         // full message chain
}

errors.As — การเดินตามสายโซ่เพื่อค้นหาชนิดข้อมูล

errors.As(err, &target) จะเดินตามสายโซ่เพื่อค้นหาข้อผิดพลาดที่สามารถกำหนดให้กับชนิดข้อมูลของ target ได้:

package main
import ("fmt"; "errors")

type StatusError struct{ Code int }
func (e *StatusError) Error() string { return fmt.Sprintf("status %d", e.Code) }

func doRequest() error {
    return fmt.Errorf("request: %w", &StatusError{503})
}

func main() {
    err := doRequest()
    var se *StatusError
    if errors.As(err, &se) {
        fmt.Println("status code:", se.Code) // 503
    }
}

การแกะข้อผิดพลาดแบบกำหนดเองสำหรับสายโซ่

นำ Unwrap() error ไปใช้กับชนิดข้อผิดพลาดแบบกำหนดเอง เพื่อรองรับ errors.Is และ errors.As:

package main
import ("fmt"; "errors")

type AppError struct {
    Op  string
    Err error
}
func (e *AppError) Error() string  { return fmt.Sprintf("%s: %v", e.Op, e.Err) }
func (e *AppError) Unwrap() error  { return e.Err }

var ErrAuth = errors.New("unauthorized")

func main() {
    err := &AppError{"login", ErrAuth}
    fmt.Println(errors.Is(err, ErrAuth)) // true
}

%v เทียบกับ %w ใน fmt.Errorf

ความแตกต่างระหว่าง %v และ %w:

package main
import ("fmt"; "errors")

func main() {
    inner := errors.New("inner")

    withV := fmt.Errorf("outer: %v", inner) // string only, no wrap
    withW := fmt.Errorf("outer: %w", inner) // wraps inner

    fmt.Println(errors.Is(withV, inner)) // false — not wrapped
    fmt.Println(errors.Is(withW, inner)) // true  — wrapped
}

การห่อข้อผิดพลาดในการใช้งานจริง: ตัวจัดการ HTTP

การห่อข้อผิดพลาดอย่างสม่ำเสมอช่วยให้การแก้ไขจุดบกพร่องในระบบใช้งานจริงทำได้ง่ายขึ้น:

package main
import "fmt"

func dbQuery(sql string) error {
    return fmt.Errorf("db.Query(%q): connection refused", sql)
}

func getUserByID(id int) (string, error) {
    if err := dbQuery("SELECT * FROM users WHERE id=?"); err != nil {
        return "", fmt.Errorf("getUserByID(%d): %w", id, err)
    }
    return "Alice", nil
}

func handler() error {
    _, err := getUserByID(1)
    if err != nil {
        return fmt.Errorf("handler: %w", err)
    }
    return nil
}

func main() { fmt.Println(handler()) }

errors.Join — การห่อหลายข้อผิดพลาด (Go 1.20 ขึ้นไป)

errors.Join สร้างข้อผิดพลาดที่ห่อข้อผิดพลาดหลายรายการ errors.Is จะตรวจสอบข้อผิดพลาดทั้งหมด:

package main
import ("fmt"; "errors")

var ErrA = errors.New("err A")
var ErrB = errors.New("err B")

func main() {
    joined := errors.Join(ErrA, ErrB)
    fmt.Println(joined)
    fmt.Println(errors.Is(joined, ErrA)) // true
    fmt.Println(errors.Is(joined, ErrB)) // true
}

แนวทางปฏิบัติที่ดีในการแกะข้อผิดพลาด

แนวทางสำหรับการห่อข้อผิดพลาด:

  • ห่อด้วย %w เพื่อเก็บข้อผิดพลาดไว้ให้โปรแกรมตรวจสอบ
  • เพิ่มชื่อการดำเนินการเพื่อให้บริบท: fmt.Errorf("funcName: %w", err)
  • อย่าห่อข้อผิดพลาดค่าประจำ หากผู้เรียกใช้จะไม่ใช้ errors.Is
  • หลีกเลี่ยงการห่อบริบทเดิมซ้ำสองครั้งในทุกระดับ
  • นำ Unwrap() ไปใช้กับชนิดข้อมูลแบบกำหนดเองเพื่อให้เข้าร่วมในสายโซ่

ตรวจสอบอย่างรวดเร็ว

การใช้ %v และ %w ใน fmt.Errorf แตกต่างกันอย่างไร

สรุปทบทวน: การห่อและการแกะข้อผิดพลาด

สรุป:

  • fmt.Errorf("ctx: %w", err) ใช้ห่อข้อผิดพลาดพร้อมบริบท
  • errors.Unwrap ดึงข้อผิดพลาดถัดไปในสายโซ่
  • errors.Is เดินตามสายโซ่เพื่อค้นหาเอกลักษณ์ที่ตรงกัน
  • errors.As เดินตามสายโซ่เพื่อค้นหาชนิดข้อมูลที่ตรงกัน
  • ชนิดข้อมูลแบบกำหนดเองนำ Unwrap() error ไปใช้
  • errors.Join ใช้ห่อข้อผิดพลาดหลายรายการ (Go 1.20 ขึ้นไป)

คำถามที่พบบ่อย

บทเรียน “การห่อและคลายข้อผิดพลาด” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การห่อและคลายข้อผิดพลาด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Go Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Go Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การห่อและคลายข้อผิดพลาด”

ใช้ %w, errors.Is และ errors.As คุณปฏิบัติ Go Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Go Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Go Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “การห่อและคลายข้อผิดพลาด” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Go Academy นี้ได้ไหม

ได้ บทเรียน Go Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. interface error
  2. การสร้างข้อผิดพลาดแบบกำหนดเอง
  3. การห่อและคลายข้อผิดพลาด
  4. panic, recover และ defer
← กลับไปที่ Go Academy