Topic 25. Assertive Programming — Lập trình quyết đoán (Pragmatic Programmer #25)
Chào mấy bạn, lại là mình đây! Hôm nay mình tiếp tục series đọc sách "The Pragmatic Programmer" (20th Anniversary Edition) với một chủ đề mà nghe tên thôi cũng thấy "máu" rồi: Assertive Programming — Lập trình quyết đoán.
Chương 4 mang tên "Pragmatic Paranoia" — hoang tưởng thực dụng. Nghe hơi kỳ nhưng ý tưởng rất hay: bạn không thể viết phần mềm hoàn hảo, vậy nên hãy xây dựng hệ thống phòng thủ. Hai bài trước mình đã nói về Design by Contract và Dead Programs Tell No Lies, hôm nay là bài thứ ba trong chuỗi này: Assertive Programming.
Ảnh: luis gomes — Pexels
Topic 25. Assertive Programming — "Chuyện đó không bao giờ xảy ra đâu!"
Mấy bạn có bao giờ nghe — hoặc tự nói — những câu như:
- "Cái app này không bao giờ được dùng ở nước ngoài đâu, cần gì i18n?"
- "Biến count làm sao mà âm được?"
- "Logging… logging làm sao mà fail?"
Đó, cái câu thần thánh "This can never happen…" chính là kẻ thù số một của lập trình viên theo tác giả cuốn sách. Và sách có một Tip cực kỳ thẳng thắn để đối phó:
Tip 39: Use Assertions to Prevent the Impossible
Dùng assertion để ngăn chặn những điều "không thể xảy ra"
Nghe đơn giản ha? Nhưng thực ra ít người làm được. Vì khi bạn nghĩ "chuyện đó không bao giờ xảy ra", bạn thường bỏ qua việc kiểm tra nó. Và rồi một ngày đẹp trời… nó xảy ra. Trong production. Lúc 3 giờ sáng. :D
Assertion là gì?
Assertion là một câu kiểm tra xem một điều kiện có đúng không. Nếu không đúng — chương trình crash ngay lập tức, kèm theo thông báo lỗi. Trong Java có assert, trong Python có assert, trong C có assert() trong <assert.h>.
Ví dụ Java:
assert value != null : "Value must not be null";
assert count >= 0 : "count should never be negative";
Ví dụ Python:
assert user is not None, "user should always be logged in here"
assert len(items) > 0, "items list should never be empty at this point"
Nhưng mà quan trọng hơn cú pháp là tư duy. Khi nào thì xài assert, khi nào thì xài error handling?
Assertion ≠ Error Handling
Đây là điểm mà nhiều bạn — kể cả mình hồi xưa — hay nhầm lẫn. Sách nhấn mạnh: assertions không phải để thay thế error handling.
- Error handling: Xử lý những lỗi có thể xảy ra trong thực tế — user nhập sai, file không tồn tại, network timeout. Đây là chuyện bình thường, code phải xử lý đẹp.
- Assertion: Kiểm tra những điều lẽ ra không bao giờ sai — nhưng nếu sai thì có bug. Đây là chuyện bất thường, crash ngay để biết mà sửa.
Sách có ví dụ về cái sai:
// SAI — dùng assert cho chuyện user nhập liệu
assert response != null; // Nếu response null thì crash app vì user?
Đoạn trên là sai vì response != null là chuyện có thể xảy ra trong thực tế (network timeout, server lỗi). Phải dùng if-else + error handling. Assertion chỉ dùng cho những thứ thực sự không thể xảy ra nếu code đúng.
Ảnh: Couleur — Pexels
Cạm bẫy: Heisenbugs trong Assertion
Sách cảnh báo một cạm bẫy rất tinh vi: assertions có side effects.
Ví dụ xấu:
while (iter.hasNext()) {
assert(iter.nextElement() != null); // Mỗi lần assert gọi nextElement() — di chuyển iterator!
Object obj = iter.nextElement(); // Đến đây thì iterator đã nhảy thêm một bước rồi
}
Đoạn code trên skip mất một nửa số phần tử vì mỗi lần lặp, iterator bị next() hai lần — một lần trong assert, một lần trong code chính. Khi tắt assertion (build production), bug này biến mất! Đó gọi là Heisenbug — bug chỉ xuất hiện khi bạn đang debug, và biến mất khi bạn ngừng debug. Quái đản thiệt chứ! :D
Cách fix: tách biệt assertion và logic:
while (iter.hasNext()) {
Object obj = iter.nextElement();
assert(obj != null); // Kiểm tra, không side effect
}
Đừng tắt Assertion trong Production!
Đây có lẽ là phần mình tâm đắc nhất trong bài này. Rất nhiều team có thói quen tắt assertion khi build production vì sợ ảnh hưởng performance. Sách bảo: đừng!
Hai lý do thường gặp để tắt assertion và câu trả lời của sách:
- "Testing tìm ra hết bug rồi, production an toàn." — Sai. Phần mềm phức tạp không thể test hết mọi permutation. Còn production thì môi trường lúc nào cũng khác — hardware lỗi, disk đầy, memory exhaustion. "Impossible" vẫn xảy ra.
- "Production là môi trường an toàn." — Sai vì lý do trên. Production là nơi khắc nghiệt nhất.
Tắt assertion trong production giống như đi trên dây cao mà không có lưới bảo vệ. Sách có một case study đắt giá: một startup nhỏ về networking để assertion bật trong production, thu thập dữ liệu failure chi tiết, fix được những bug cực kỳ hiếm gặp. Kết quả? Phần mềm của họ cực kỳ ổn định và sau đó công ty được mua lại với giá trăm triệu đô.
Performance thì sao? Chỉ tắt những assertion nào thực sự ảnh hưởng performance. Còn lại, cứ bật hết.
Bài tập thú vị: "Impossible" mà có thể xảy ra?
Sách có Exercise 16 hỏi: trong những chuyện "không thể" sau, cái nào thực sự có thể xảy ra?
- Một tháng có ít hơn 28 ngày
- Error code từ system call: "không thể truy cập thư mục hiện tại"
- Trong C++:
a = 2; b = 3;mà(a + b)không bằng 5 - Một hình tam giác có tổng góc trong khác 180°
- Một phút không có 60 giây
(a + 1) <= a
Nghĩ thử đi mấy bạn! Có những chuyện tưởng impossible mà thực ra trong môi trường máy tính lại có thể — như integer overflow, lỗi hardware, leap second (phút có 61 giây), không gian phi Euclid trong đồ họa máy tính… Đó chính xác là lý do tại sao bạn cần assertion.
Ảnh: Lukas — Pexels
Cảm nhận của mình
Mình thấy bài này đặc biệt hay vì nó đánh trúng tâm lý của rất nhiều lập trình viên (kể cả mình). Chúng ta thường lạc quan thái quá — "cái này không bao giờ fail đâu", "user không bao giờ làm vậy đâu" — và rồi production dạy cho chúng ta một bài học nhớ đời.
Mình nhớ có lần code một feature xử lý file upload. Mình nghĩ: "hệ thống chỉ nhận file ảnh thôi, không cần kiểm tra extension kỹ đâu". Kết quả? Một bạn tester upload file .pdf, code crash, và mình mất cả buổi chiểu debug. Nếu mình chịu khó assert ngay từ đầu — "extension phải là jpg/png/gif" — lỗi đã được phát hiện ngay lúc dev, không đợi tới QA.
Nhưng cũng phải nói thật: assertive programming không phải chuyện dễ áp dụng 100%. Trong codebase thực tế, ranh giới giữa "chuyện không thể xảy ra" và "chuyện có thể xảy ra" đôi khi rất mờ. Đặc biệt với Python và JavaScript — không có built-in assertion mạnh, phải tự viết hoặc dùng thư viện.
Tuy nhiên, ít nhất thì tư duy "đặt câu hỏi cho những điều tưởng như hiển nhiên" đã thay đổi cách mình viết code. Mỗi lần nghĩ "cái này không bao giờ sai", mình tự hỏi: "Lỡ nó sai thì sao?" — và thêm một cái assert. Chi phí thấp, lợi ích cao.
Kết
Topic 25 — Assertive Programming — nhắc nhở chúng ta rằng: đừng bao giờ tin vào câu "chuyện đó không bao giờ xảy ra". Hãy viết code với tinh thần hoang tưởng thực dụng: tin rằng mọi thứ đều có thể sai, và dùng assertion để tự bảo vệ mình.
Như sách nói: "Let's not practice this kind of self-deception, particularly when coding."
Bài sau mình sẽ tiếp tục với phần còn lại của Chương 4 — còn Topic 26: How to Balance Resources — quản lý tài nguyên, một chủ đề cực kỳ thực tế mà dân backend như mình gặp hằng ngày!
Hẹn gặp lại mấy bạn nha! 👋