Giải thích cách sử dụng defer kết hợp với panic và recover để xử lý lỗi trong Golang.
Trong Golang, bộ ba defer, panic và recover chính là cơ chế sống còn để xử lý các lỗi cực kỳ nghiêm trọng (runtime errors) – những lỗi mà nếu để mặc sẽ làm toàn bộ ứng dụng bị crash.
Dưới đây là cách chúng phối hợp với nhau trong một hệ thống backend thực tế.
1. Vai trò của từng "nhân vật"
panic: Khi chương trình gặp một sự cố không thể tự phục hồi (ví dụ: truy cập mảng quá giới hạn, dereference một pointer nil, hoặc bạn chủ động gọipanic()), luồng thực thi bình thường của hàm sẽ bị dừng lại ngay lập tức.defer: Điểm đặc biệt của Go là dù hàm có bịpaniccắt ngang, ngôn ngữ này vẫn đảm bảo tất cả các lệnhdeferđã được đăng ký trước đó sẽ được chạy (theo thứ tự LIFO).recover: Đây là hàm duy nhất có thể "chặn đứng" sự lây lan củapanic. Nó chụp lại giá trị đã được truyền vàopanicvà giúp Goroutine đó không bị sập. Điều kiện bắt buộc:recoverchỉ có tác dụng khi nó được gọi bên trong một hàmdefer.
2. Cách chúng phối hợp trong thực tế
Giả sử bạn đang viết một worker xử lý dữ liệu chạy ngầm. Nếu một job bị lỗi nghiêm trọng, bạn chỉ muốn log lỗi đó lại và bỏ qua, chứ không muốn toàn bộ tiến trình (process) chứa worker đó bị chết theo.
package main
import (
"fmt"
)
// Giả lập một worker xử lý logic backend
func processTransaction() {
// 1. Đăng ký defer để bọc lấy recover. Luôn đặt lên đầu hàm.
defer func() {
if r := recover(); r != nil {
// Nếu có panic xảy ra, r sẽ chứa giá trị của panic đó (khác nil)
fmt.Printf("[Cảnh báo] Đã bắt được panic: %v\n", r)
// Ở đây bạn có thể bắn log lỗi lên Kibana, Sentry, hoặc dọn dẹp tài nguyên
}
}()
fmt.Println("Worker bắt đầu xử lý giao dịch...")
// 2. Gặp lỗi nghiêm trọng giữa chừng (ví dụ: mất kết nối DB)
panic("Mất kết nối tới PostgreSQL đột ngột!")
// 3. Đoạn code phía sau panic sẽ không bao giờ được chạy
fmt.Println("Lưu giao dịch thành công (Dòng này sẽ bị bỏ qua)")
}
func main() {
fmt.Println("--- Khởi động hệ thống ---")
// Gọi hàm có khả năng sinh lỗi
processTransaction()
// 4. Nhờ có recover(), chương trình chính vẫn sống sót và chạy tiếp
fmt.Println("--- Hệ thống vẫn hoạt động bình thường ---")
}
Kết quả khi chạy đoạn code trên:
--- Khởi động hệ thống ---
Worker bắt đầu xử lý giao dịch...
[Cảnh báo] Đã bắt được panic: Mất kết nối tới PostgreSQL đột ngột!
--- Hệ thống vẫn hoạt động bình thường ---
3. Lưu ý quan trọng khi thiết kế hệ thống
- Go HTTP Server (
net/http): Bạn không cần tự viếtrecovercho mọi API endpoint. Go router mặc định đã tích hợp sẵnrecovercho từng request. Nếu một API bị panic, chỉ request đó nhận về mã lỗi 500, server vẫn tiếp tục phục vụ các request khác bình thường. - Tránh lạm dụng: Khác với
try...catchtrong các ngôn ngữ khác (Java, Node.js), bạn không nên dùngpanic/recoverđể kiểm soát luồng logic kinh doanh (ví dụ: validate input sai, mật khẩu không đúng). Hãy sử dụng cú pháp trả vềerror(if err != nil) truyền thống.panicchỉ nên dùng cho những lỗi thực sự nằm ngoài tầm kiểm soát của code.
All rights reserved