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)
}
}
}