Series Clean Code Thực chiến #4: Nghệ thuật Xử lý Lỗi – Đừng để hệ thống "chết đứng"
1. Hãy ném Exception (Ngoại lệ), Đừng trả về Mã lỗi (Error Codes)
Trước đây, lập trình viên thường dùng cách trả về một con số (-1, 0) hoặc một mảng trạng thái để báo lỗi. Cách làm này tạo ra một thảm họa gọi là Error Handling Clutter (Sự lộn xộn do xử lý lỗi). Người gọi hàm sẽ bị ép phải kiểm tra lỗi ngay lập tức, khiến luồng logic chính bị băm nát.
Bad Code (Trả về mã lỗi):
public function processTapCard(Card $card) {
$status = $this->chargeFare($card);
if ($status === 'SUCCESS') {
$turnstileStatus = $this->openGate();
if ($turnstileStatus === 'GATE_OPENED') {
return true;
} else {
return 'ERROR_GATE_STUCK';
}
} else {
return 'ERROR_INSUFFICIENT_BALANCE';
}
}
Clean Code (Ném Exception): Thay vì dùng if/else để check kết quả, hãy ném thẳng một Exception. Luồng đi chính (Happy path) sẽ không còn bị ngắt quãng.
public function processTapCard(Card $card) {
try {
$this->chargeFare($card);
$this->openGate();
return true;
} catch (InsufficientBalanceException $e) {
// Ghi log hoặc báo tín hiệu đèn đỏ
} catch (GateStuckException $e) {
// Báo nhân viên ga ra hỗ trợ
}
}
2. Tránh xa try/catch rải rác: Hãy xử lý tập trung
Dù try/catch tốt hơn if/else, nhưng nếu bạn rải nó ở mọi class, mọi hàm, file code của bạn trông sẽ rất bẩn.
Trong kiến trúc hiện đại (đặc biệt là với các framework như Laravel), tư duy chuẩn là: Ném lỗi ở tầng dưới cùng, và Bắt lỗi ở một nơi duy nhất trên cùng.
Khi bạn viết logic trừ tiền trong Repository hoặc Service, nếu thấy thẻ hết tiền, cứ thẳng tay throw new InsufficientBalanceException(). Bạn không cần bọc try/catch ngay tại đó. Hãy để Laravel's Global Exception Handler (nằm trong app/Exceptions/Handler.php) tự động hứng cái lỗi đó, và format nó thành một chuỗi JSON đẹp đẽ (kèm HTTP status 400) để trả về cho Frontend.
Code logic của bạn sẽ hoàn toàn "sạch bóng" try/catch rườm rà.
3. Đừng bao giờ trả về Null (The Billion Dollar Mistake)
Việc một hàm tìm kiếm trả về null khi không thấy dữ liệu là nguồn gốc của 80% các lỗi sập hệ thống (Call to a member function on null).
Bad Code:
$transactions = $this->getTransactionsByStation($stationId);
// Lại phải mất công kiểm tra null, nếu quên là hệ thống "chết đứng"
if ($transactions !== null) {
foreach ($transactions as $txn) {
$txn->sync();
}
}
Clean Code: Nếu một hàm lấy danh sách không tìm thấy gì, hãy trả về một mảng rỗng [] hoặc một Collection rỗng.
$transactions = $this->getTransactionsByStation($stationId);
// Nếu mảng rỗng, vòng lặp foreach đơn giản là bỏ qua, không gây lỗi!
foreach ($transactions as $txn) {
$txn->sync();
}
Mẹo bổ sung: Nếu hàm cần trả về một Đối tượng (Object) thay vì mảng, hãy áp dụng Null Object Pattern (Tạo ra một class NullUser hoặc GuestUser với các hành vi mặc định thay vì trả về null trơ trọi).
4. Đừng truyền Null vào hàm
Trả về null đã tệ, việc cho phép truyền null vào như một tham số còn tồi tệ hơn.
// Hàm này tính toán phí, và nó cho phép mã giảm giá là null
public function calculateFare($distance, $discountCode = null) {
if ($discountCode !== null) {
// Áp dụng giảm giá
}
// ...
}
Khi bạn thiết kế hàm cho phép nhận null, bạn đang tự ép mình phải viết thêm các lệnh if/else để kiểm tra đầu vào. Đồng thời, nó ám chỉ rằng hàm này đang làm 2 việc khác nhau (một việc có giảm giá, một việc không có).
Hãy tách nó ra thành 2 hàm riêng biệt, hoặc sử dụng tính năng Đa hình (Polymorphism) để xử lý.
Code sạch là code không giấu giếm rủi ro, không đùn đẩy trách nhiệm kiểm tra lỗi (null checks) cho người đồng nghiệp sẽ sử dụng lại hàm đó. Bằng việc phân tách rõ ràng luồng chạy chính và luồng xử lý lỗi, mã nguồn của bạn sẽ trở nên kiên cố, dễ đọc và cực kỳ dễ test!
All rights reserved