Error handling
Error handling: Thành thạo cách xử lý lỗi trong Go (error wrapping, custom errors, panic/recover).
🎯 Tư duy về Error trong Go
Go không có exception như Java. Thay vào đó, error là một giá trị (
error
là một interface).Error trong Go phải được kiểm soát có chủ đích, không thể lờ đi.
Không dùng panic để xử lý lỗi logic thông thường.
1. 📦 Kiểu error cơ bản
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
Sử dụng:
res, err := divide(10, 0)
if err != nil {
log.Println("Error:", err)
return
}
fmt.Println("Result:", res)
2. 🎨 Custom error types
Tạo error có ngữ cảnh riêng và phân loại lỗi:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("Validation error on field '%s': %s", e.Field, e.Msg)
}
func validateUsername(username string) error {
if len(username) < 5 {
return &ValidationError{Field: "username", Msg: "too short"}
}
return nil
}
Xử lý:
err := validateUsername("abc")
if vErr, ok := err.(*ValidationError); ok {
log.Printf("Validation failed: %v\n", vErr)
}
3. 🧩 Error Wrapping (fmt.Errorf
, errors.Join
, errors.Unwrap
)
fmt.Errorf
, errors.Join
, errors.Unwrap
)Go 1.13+ hỗ trợ error wrapping để giữ lại ngữ cảnh lỗi gốc:
a. Wrapping error
func readFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err)
}
_ = data
return nil
}
b. Unwrap và check loại lỗi
err := readFile("notfound.txt")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File not found")
}
4. 🪓 Panic & Recover – Dùng đúng lúc
❌ Không dùng panic trong logic nghiệp vụ
✅ Dùng panic cho exception không recover được: vi phạm hợp đồng, lỗi dev (not user)
⚠️ Recover dùng để chặn hệ thống sập
func safeExecute(f func()) {
defer func() {
if r := recover(); r != nil {
log.Println("Recovered from panic:", r)
}
}()
f()
}
5. 📌 Best Practices từ Senior
✅ Luôn trả lỗi, không panic:
data, err := getData()
if err != nil {
return nil, fmt.Errorf("getData failed: %w", err)
}
✅ Phân loại lỗi: dùng custom error type hoặc sentinel error
var ErrUserNotFound = errors.New("user not found")
✅ Logging lỗi ở layer cao nhất (ex: handler, main) – tránh log trùng nhiều tầng.
✅ Đừng nuốt lỗi:
_, err := doSomething()
if err != nil {
// đừng ignore
}
6. 📁 Mô hình hoá Error theo tầng ứng dụng
repo
layer: returnErrNotFound
,ErrDB
, custom errorsservice
layer: wrap lại với contexthandler
layer: phân tích lỗi và trả HTTP status tương ứng
7. 🧪 Unit test với error
func TestDivide_ByZero(t *testing.T) {
_, err := divide(10, 0)
require.Error(t, err)
assert.EqualError(t, err, "cannot divide by zero")
}
Kết
Ghi log lỗi rõ ràng
log
, zap
, logrus
Theo dõi lỗi production
Sentry
, Honeybadger
, OpenTelemetry
Gỡ lỗi, trace
pprof
, panic/recover
, errors.Wrap
Viết error semantic rõ ràng
Custom errors, errors.Is/As/Unwrap
Last updated