Trong nhiều dự án web/app, xử lý lỗi thường bị xem là phần “làm sau”: cứ để hệ thống báo lỗi mặc định, hoặc đơn giản là trả về thông báo chung chung như “Có lỗi xảy ra”. Cách này có thể tạm ổn khi ứng dụng còn nhỏ, nhưng khi có người dùng thật, lỗi không rõ ràng sẽ gây khó chịu cho người dùng và làm đội phát triển mất nhiều thời gian truy vết. Một hệ thống xử lý lỗi tốt không chỉ là bắt try/catch ở vài chỗ. Nó cần giúp ba nhóm cùng lúc: người dùng hiểu họ nên làm gì tiếp theo, lập trình viên có đủ thông tin để sửa lỗi, và hệ thống không vô tình lộ dữ liệu nhạy cảm. 1. Phân loại lỗi trước khi xử lý Không phải lỗi nào cũng giống nhau. Nên phân biệt tối thiểu các nhóm sau: - Lỗi do người dùng nhập sai: email không hợp lệ, mật khẩu quá ngắn, thiếu trường bắt buộc. - Lỗi nghiệp vụ: tài khoản không đủ số dư, mã giảm giá đã hết hạn, người dùng không có quyền truy cập. - Lỗi hệ thống: mất kết nối database, API bên thứ ba không phản hồi, lỗi logic trong code. - Lỗi bảo mật hoặc quyền hạn: token hết hạn, truy cập tài nguyên không thuộc về người dùng. Việc phân loại giúp ta quyết định thông báo nào nên hiển thị, mã trạng thái HTTP nào nên trả về, và log ở mức độ nào. 2. Thông báo lỗi cho người dùng cần rõ nhưng không thừa Một lỗi tốt nên trả lời được câu hỏi: “Tôi cần làm gì tiếp?”. Ví dụ: Không nên: “Invalid request” Nên: “Email không đúng định dạng. Vui lòng kiểm tra lại, ví dụ: ten@example.com.” Không nên: “Server error: SQLSTATE...” Nên: “Hệ thống đang gặp sự cố khi xử lý yêu cầu. Vui lòng thử lại sau ít phút.” Với lỗi kỹ thuật, tuyệt đối không hiển thị stack trace, câu truy vấn SQL, đường dẫn file nội bộ hoặc thông tin cấu hình. Những dữ liệu này nên được ghi vào log dành cho lập trình viên, không đưa ra giao diện. 3. API nên trả lỗi theo cấu trúc nhất quán Nếu làm backend cho web hoặc mobile app, cấu trúc lỗi nhất quán sẽ giúp frontend xử lý dễ hơn. Ví dụ một response lỗi có thể gồm: - code: mã lỗi nội bộ, ví dụ USER_NOT_FOUND, VALIDATION_ERROR. - message: thông báo ngắn gọn có thể hiển thị. - details: thông tin chi tiết theo từng trường, nếu là lỗi validation. - request_id: mã truy vết để đối chiếu với log. Ví dụ thực tế: khi form đăng ký sai dữ liệu, backend không nên chỉ trả “Validation failed”. Thay vào đó, có thể trả danh sách lỗi theo field như email đã tồn tại, password chưa đủ độ dài, username chứa ký tự không hợp lệ. Frontend sẽ dễ hiển thị lỗi ngay dưới từng ô nhập. 4. Ghi log đủ dùng, đừng ghi mọi thứ vô tội vạ Log là công cụ cứu mạng khi sự cố xảy ra, nhưng log kém chất lượng cũng khiến việc điều tra khó hơn. Một log hữu ích thường có: - Thời điểm xảy ra lỗi. - Mức độ: info, warning, error, critical. - request_id hoặc trace_id. - user_id nếu có, nhưng tránh ghi dữ liệu nhạy cảm. - endpoint, method, tham số quan trọng đã được che hoặc lọc. - thông tin exception và stack trace ở môi trường phù hợp. Không nên ghi password, access token, refresh token, mã OTP, thông tin thẻ thanh toán hoặc dữ liệu cá nhân không cần thiết. Nếu bắt buộc cần log một phần để điều tra, hãy masking, ví dụ chỉ giữ vài ký tự cuối. 5. Dùng request_id để truy vết nhanh hơn Một mẹo rất thực tế là gắn request_id cho mỗi request. Khi người dùng báo: “Tôi bấm thanh toán thì bị lỗi”, hệ thống có thể hiển thị hoặc lưu lại mã lỗi dạng ngắn. Lập trình viên dùng mã đó để tìm đúng log tương ứng, thay vì lọc hàng nghìn dòng theo thời gian. Nếu ứng dụng có nhiều dịch vụ như frontend, backend, worker, payment service, request_id càng quan trọng. Nó giúp nối các sự kiện rời rạc thành một luồng xử lý có thể đọc được. 6. Đừng quên lỗi ở tác vụ nền Nhiều lỗi không xảy ra ngay lúc người dùng gửi request, mà xuất hiện trong queue, cron job hoặc worker: gửi email thất bại, đồng bộ dữ liệu lỗi, xử lý ảnh bị hỏng. Những lỗi này nếu không được theo dõi sẽ âm thầm tích tụ. Nên có cơ chế retry hợp lý, giới hạn số lần thử lại, lưu trạng thái failed job và cảnh báo khi lỗi lặp lại. Với các tác vụ quan trọng như thanh toán, tạo hóa đơn, gửi thông báo pháp lý, cần thiết kế idempotency để retry không tạo ra dữ liệu trùng. 7. Một checklist ngắn có thể áp dụng ngay - Tắt hiển thị lỗi kỹ thuật ở production. - Chuẩn hóa format lỗi cho API. - Viết thông báo lỗi thân thiện cho các tình huống phổ biến. - Log có request_id và mức độ rõ ràng. - Không log dữ liệu nhạy cảm. - Theo dõi lỗi trong queue, cron job, worker. - Có trang lỗi 404, 403, 500 dễ hiểu thay vì màn hình trắng. - Kiểm thử cả trường hợp thất bại, không chỉ kiểm thử luồng thành công. Kết luận Xử lý lỗi tốt không làm ứng dụng hết lỗi, nhưng giúp lỗi trở nên kiểm soát được. Người dùng biết họ nên thử lại, sửa dữ liệu hay liên hệ hỗ trợ. Lập trình viên có đủ dấu vết để tìm nguyên nhân. Dự án càng lớn, việc đầu tư vào thông báo lỗi, cấu trúc response và log càng đáng giá, vì nó giảm rất nhiều thời gian “đoán mò” khi sự cố xảy ra.