Error Handling

Triết lý xử lý lỗi của Go

Go không dùng exception (try/catch). Thay vào đó, lỗi được trả về như giá trị bình thường, buộc lập trình viên phải xử lý tường minh.

// Mẫu chuẩn: (kết_quả, error)
func docFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err  // truyền lỗi lên trên
    }
    return data, nil
}

// Gọi hàm
data, err := docFile("config.json")
if err != nil {
    log.Fatal("Không đọc được file:", err)
}

Tạo lỗi

import "errors"
import "fmt"

// Cách 1: errors.New — lỗi tĩnh
var ErrDivByZero = errors.New("chia cho zero")

// Cách 2: fmt.Errorf — lỗi có context
err := fmt.Errorf("parse config: %w", originalErr)

// Cách 3: Custom error type
type ValidationError struct {
    Field   string
    Message string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation: %s — %s", e.Field, e.Message)
}

errors.Is và errors.As

// errors.Is — kiểm tra lỗi là loại gì (kể cả wrapped)
if errors.Is(err, ErrDivByZero) {
    fmt.Println("Lỗi chia cho zero")
}

// errors.As — lấy ra custom error type
var valErr *ValidationError
if errors.As(err, &valErr) {
    fmt.Println("Field lỗi:", valErr.Field)
}

// Wrap lỗi với %w để dùng với Is/As
err = fmt.Errorf("xử lý đơn hàng #%d: %w", orderID, ErrDivByZero)
errors.Is(err, ErrDivByZero)  // true (dù đã wrapped)

Panic và Recover

// panic: dừng goroutine ngay lập tức (dùng cho lỗi không thể phục hồi)
func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("giá trị phải dương, nhận %d", n))
    }
    return n
}

// recover: bắt panic trong defer (dùng để không crash toàn bộ server)
func safeCall(f func()) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered: %v", r)
        }
    }()
    f()
    return nil
}
example.go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

var ErrAmountNegative = errors.New("số tiền không được âm")
var ErrInsufficientFunds = errors.New("số dư không đủ")

type TransactionError struct {
	Op     string
	Amount float64
	Err    error
}

func (e *TransactionError) Error() string {
	return fmt.Sprintf("giao dịch [%s] %.0fđ: %v", e.Op, e.Amount, e.Err)
}
func (e *TransactionError) Unwrap() error { return e.Err }

type Account struct {
	Name    string
	Balance float64
}

func (a *Account) Deposit(amount float64) error {
	if amount < 0 {
		return &TransactionError{"nạp tiền", amount, ErrAmountNegative}
	}
	a.Balance += amount
	return nil
}

func (a *Account) Withdraw(amount float64) error {
	if amount < 0 {
		return &TransactionError{"rút tiền", amount, ErrAmountNegative}
	}
	if amount > a.Balance {
		return &TransactionError{"rút tiền", amount, ErrInsufficientFunds}
	}
	a.Balance -= amount
	return nil
}

func parseAmount(s string) (float64, error) {
	n, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0, fmt.Errorf("parse %q: %w", s, err)
	}
	return n, nil
}

func main() {
	acc := &Account{Name: "Nguyễn An", Balance: 1_000_000}
	fmt.Printf("Số dư ban đầu: %.0fđ\n\n", acc.Balance)

	ops := []struct {
		op     string
		amount string
	}{
		{"nạp", "500000"},
		{"rút", "200000"},
		{"rút", "2000000"},
		{"nạp", "-100"},
		{"rút", "abc"},
	}

	for _, o := range ops {
		amount, err := parseAmount(o.amount)
		if err != nil {
			fmt.Printf("❌ Parse lỗi: %v\n", err)
			continue
		}

		var txErr error
		if o.op == "nạp" {
			txErr = acc.Deposit(amount)
		} else {
			txErr = acc.Withdraw(amount)
		}

		if txErr != nil {
			fmt.Printf("❌ %v\n", txErr)
			if errors.Is(txErr, ErrInsufficientFunds) {
				fmt.Printf("   → Cần nạp thêm: %.0fđ\n", amount-acc.Balance)
			}
		} else {
			fmt.Printf("✓  %s %.0fđ → Số dư: %.0fđ\n", o.op, amount, acc.Balance)
		}
	}
}