Sửa bằng giả thuyết, không sửa mò
Đổi một dấu, chạy lại, vẫn sai, đổi tiếp — sửa mò không hội tụ vì không lượt nào loại bớt được nguyên nhân. Vòng đoán rồi kiểm chứng thì có.
TL;DR: Đổi một dấu, chạy lại, vẫn sai, đổi tiếp — sửa mò không hội tụ vì không lượt nào loại bớt được nguyên nhân. Bài này thay bằng vòng bốn bước: đặt giả thuyết kiểm chứng được, chọn phép kiểm rẻ nhất có thể bác bỏ nó, chạy rồi đọc kết quả, rồi thu hẹp nghi ngờ dù giả thuyết đúng hay sai. Hai dụng cụ kiểm chứng là print và bảng trace, chọn theo việc cần soi một giá trị hay nhiều biến qua nhiều vòng lặp. Ca mẫu là một lỗi logic thật: đặt tong = 0 nhầm vào bên trong thân vòng lặp, khiến chương trình chạy trót lọt nhưng in ra đúng phần tử cuối.
Bạn viết xong một đoạn code, chạy, sai. Đổi dấu > thành >=, chạy lại, vẫn sai. Đổi tên biến, chạy lại, vẫn sai. Đổi ngược lại đúng dòng vừa sửa, chạy lại — bỗng dưng đúng, và bạn không biết vì sao nó đúng.
Đây gọi là sửa mò, và nó không hội tụ. Giả sử đoạn code mười dòng của bạn có năm chỗ khả nghi. Mỗi lượt sửa mò đổi đúng một chỗ, chạy lại, thấy vẫn sai — nhưng bạn KHÔNG loại được chỗ nào trong bốn chỗ còn lại, vì chưa hề kiểm tra riêng chỗ đó có phải thủ phạm hay không. Sau mười lượt, vùng nghi ngờ vẫn là năm chỗ y hệt lúc đầu — bạn chỉ đang đoán, không đang thu hẹp.
Có một cách khác: đoán CÓ CHỦ ĐÍCH rồi kiểm tra đúng đoán đó — dù đúng dù sai đều biết thêm một điều mới. Đó là nội dung bài này.
Bảng trace ở bài đó là công cụ để đóng vai máy, đọc hiểu một đoạn code chạy đúng. Ở bài này, nó đổi vai: từ công cụ để HIỂU code thành công cụ để KIỂM CHỨNG một đoán về code đang chạy sai.
Năm ca biên ở bài đó — rỗng, một phần tử, số 0, số âm, trùng nhau — chính là nguồn giả thuyết có sẵn: nghi code sai, việc đầu tiên đáng thử là những ca đó, thay vì đoán ngẫu nhiên.
Bài này lo đúng một việc: biến print và bảng trace thành công cụ kiểm chứng giả thuyết, thay vì chỉ để đọc hiểu code.
1. Analogy — bóng đèn không sáng
Đèn phòng khách nhà bạn không sáng. Bạn không tháo tung cả hệ thống điện (ổ cắm, dây dẫn, cầu dao, bóng đèn) ra kiểm tra cùng lúc. Bạn đoán CÓ CHỦ ĐÍCH: "chắc là bóng đèn cháy", rồi kiểm chứng bằng cách rẻ nhất: thay một bóng đã biết còn tốt vào đúng ổ đó. Bật công tắc, nhìn kết quả. Sáng: đúng là bóng cháy. Không sáng: loại bóng đèn, chuyển sang giả thuyết kế tiếp, có khi là cầu dao.
| Đèn không sáng | Code chạy sai |
|---|---|
| Giả thuyết: "chắc là bóng đèn cháy" | Giả thuyết: "tong bị đặt lại về 0 ở mỗi vòng lặp" |
| Kiểm chứng rẻ nhất: thay một bóng đã biết còn tốt vào đúng ổ đó | Kiểm chứng rẻ nhất: in giá trị tong ngay trong thân vòng lặp |
| Bật công tắc, nhìn kết quả | Chạy chương trình, đọc output |
| Sáng: đúng là bóng cháy. Không sáng: loại bóng đèn, chuyển giả thuyết khác | tong không tăng dần qua các dòng in: giả thuyết đúng. tong tăng dần bình thường: loại giả thuyết, chuyển hướng khác |
Bạn không tháo tung cả hệ thống điện chỉ vì một bóng không sáng. Bạn thử đúng chỗ nghi ngờ nhất trước — dù kết quả thế nào, bạn cũng biết thêm một điều mà lúc trước chưa biết.
2. Vòng bốn bước
Vòng bốn bước thay sửa mò bằng bốn thao tác lặp lại tới khi tìm ra nguyên nhân thật:
- Giả thuyết kiểm chứng được. "Tôi nghĩ tong bị đặt lại về 0 ở mỗi vòng lặp" có một quan sát cụ thể sẽ xác nhận hoặc bác bỏ nó. "Chắc vòng lặp có vấn đề" thì không — không quan sát nào bác bỏ được một câu mơ hồ.
- Phép kiểm chứng rẻ nhất bác bỏ được nó. Không cần đọc lại toàn bộ hàm; chỉ cần một dòng print đúng chỗ, hoặc một hàng bảng trace tại điểm nghi ngờ.
- Chạy, đọc kết quả. Đọc đúng con số hiện ra, không đọc theo điều bạn MUỐN thấy.
- Thu hẹp — dù đúng dù sai. Đúng: tìm ra thủ phạm, sửa xong. Sai: loại được một chỗ khỏi danh sách nghi ngờ, còn ít chỗ hơn để thử tiếp. Đây là thứ sửa mò không có.
So với năm chỗ nghi ngờ ở đầu bài, mỗi vòng loại bớt một chỗ — sau năm lượt, không phải mười, còn lại đúng một thủ phạm.

3. Print hay bảng trace — chọn thế nào?
print rẻ và nhanh: thêm một dòng, chạy lại, đọc giá trị TẠI đúng một điểm nghi ngờ. Dùng khi giả thuyết chỉ nhắm một biến ở một chỗ cụ thể.
Bảng trace tốn công hơn nhưng cho một bức tranh rộng hơn: nhiều biến, qua nhiều vòng lặp, trên cùng một bảng. Dùng khi giả thuyết liên quan tới CÁCH một giá trị biến đổi qua thời gian — như "tong có cộng dồn qua từng vòng hay bị đặt lại?" — một print tại một điểm không đủ trả lời câu đó.
4. Thử đoán — tổng điểm một lớp bốn bạn
Chương trình sau tính tổng điểm của bốn bạn trong danh sách diem:
diem = [8, 5, 9, 6]
for d in diem:
tong = 0
tong = tong + d
print(tong)
Đừng chạy vội. Nhìn kỹ bốn dòng thân vòng lặp, đoán xem dòng print(tong) cuối cùng in ra số nào. Viết ra con số bạn tin chắc trước khi đọc tiếp.
Output thật:
6
6 không phải ngẫu nhiên — nó đúng bằng phần tử CUỐI của diem. Chương trình chạy trót lọt, không traceback: đây là lỗi logic, khác lỗi runtime ở bài trước — máy không có chỗ nào để dừng và báo bạn biết nó sai.
Giờ tự chạy vòng bốn bước cho đúng chương trình này — làm từng bước một, đối chiếu xong mới sang bước kế.
Vì sao tong ra đúng bằng phần tử cuối, không phải tổng bốn số? Tự viết một giả thuyết kiểm chứng được trước khi mở đối chiếu.
Giả thuyết: "tong bị đặt lại về 0 ở mỗi vòng lặp, nên giá trị cộng dồn từ vòng trước bị xoá trước khi cộng số mới."
Phép kiểm chứng rẻ nhất cho giả thuyết đó là gì, đặt ở dòng nào?
Kiểm chứng rẻ nhất: thêm print(tong) ngay sau dòng cộng, bên trong thân vòng lặp, để thấy tong ở TỪNG vòng thay vì chỉ giá trị cuối:
diem = [8, 5, 9, 6]
for d in diem:
tong = 0
tong = tong + d
print(tong)
print("Ket qua cuoi:", tong)
Đoán bốn dòng print(tong) bên trong vòng lặp sẽ in ra số nào.
Chạy, đọc kết quả. Output thật:
8
5
9
6
Ket qua cuoi: 6
So bốn dòng vừa in với bốn phần tử của diem: giả thuyết Bước 1 đúng hay sai? Sửa dòng nào?
Thu hẹp. Bốn dòng in ra đúng bằng bốn phần tử của diem, không hề cộng dồn — dòng thứ hai là 5, không phải 8 + 5 = 13. Giả thuyết ĐÚNG: mỗi vòng, tong bị đặt lại về 0 trước khi cộng, nên nó chỉ giữ giá trị phần tử vừa xử lý. Sửa bằng cách đưa dòng tong = 0 ra khỏi thân vòng lặp, đặt trước dòng for:
diem = [8, 5, 9, 6]
tong = 0
for d in diem:
tong = tong + d
print(tong)
Output thật:
28
28 mới là tổng thật.
5. Tới lượt bạn
Cùng vòng bốn bước, áp cho một bug khác: đếm xem có bao nhiêu bạn đạt điểm từ 5 trở lên trong danh sách diem.
diem = [4, 7, 9, 3]
dem_dat = 0
for d in diem:
if d >= 5:
dem_dat = 1
print(dem_dat)6. Bẫy thường gặp
❌ Nhầm 1 — đổi một chỗ rồi chạy lại, không kèm giả thuyết kiểm chứng được. Thấy vẫn sai thì đổi tiếp, hết sai thì dừng mà không biết vì sao — đúng cảnh mở bài.
✅ Trước khi đổi bất kỳ dòng nào, viết thành một câu: "tôi nghĩ biến X sai vì lý do Y" — không viết được câu đó thì chưa nên sửa.
❌ Nhầm 2 — đọc kết quả kiểm chứng theo điều mình muốn thấy. Thấy print(tong) in ra 8, 5, 9, 6 mà vẫn tự nhủ "chắc lỗi ở chỗ khác" — bỏ qua bằng chứng trước mắt.
✅ Đọc đúng con số hiện ra trước, so với điều giả thuyết dự đoán, rồi mới kết luận.
❌ Nhầm 3 — chọn print khi cần thấy cả một quá trình qua nhiều vòng. print(tong) đặt SAU vòng lặp chỉ cho một con số cuối, không phân biệt "không cộng dồn" với "cộng dồn đúng nhưng tình cờ ra cùng số".
✅ Nghi biến đổi qua nhiều vòng thì đặt print BÊN TRONG vòng lặp, hoặc dùng bảng trace.
7. 📚 Đào sâu — vì sao vòng bốn bước giống phương pháp khoa học
Vòng bốn bước là dạng thu nhỏ của phương pháp khoa học: đặt giả thuyết, tìm phép thử có thể BÁC BỎ nó, rồi kết luận theo kết quả thử — không theo niềm tin ban đầu.
Chữ đáng nhớ nhất là "bác bỏ được": "chắc vòng lặp có vấn đề" không phép thử nào bác bỏ được, còn "tong bị đặt lại mỗi vòng" thì một dòng print bác bỏ hoặc xác nhận ngay — đó là lý do sửa mò không tiến bộ được lượt nào.
8. Liên hệ các bài khác
- Bài 1 — Ba loại lỗi — lỗi logic là một trong ba loại phân biệt ở đó; ca mẫu bài này là ví dụ cụ thể của loại đó.
- Bài 2 — Đọc thông báo lỗi — traceback giúp chẩn đoán lỗi runtime; lỗi logic không có traceback, nên cần vòng bốn bước.
- Bài 4 module 1 — Bảng trace — công cụ dùng lại nguyên vẹn, chỉ đổi vai từ hiểu code sang kiểm chứng giả thuyết.
- Bài 6 module 3 — Ca biên — nguồn giả thuyết có sẵn: thử đúng năm ca biên trước khi đoán ngẫu nhiên.
- Bài 4 — Kẹt là bình thường — khi vòng bốn bước chạy nhiều lượt vẫn chưa ra manh mối, bài sau nói khi nào nên dừng và nhờ người khác.
9. Tóm tắt
- Sửa mò không hội tụ vì không lượt nào loại được nguyên nhân; vòng bốn bước loại được ít nhất một chỗ mỗi lần, kể cả khi giả thuyết sai.
- Câu "chắc vòng lặp có vấn đề" không trỏ tới một lệnh print nào để mà kiểm, nên nó để bạn đứng nguyên chỗ cũ.
- print soi đúng một điểm; bảng trace soi cả một quá trình qua nhiều vòng lặp — chọn công cụ theo câu hỏi đang cần trả lời, không theo thói quen.
- tong = 0 đặt sai chỗ, bên trong thân vòng lặp thay vì trước nó, là lỗi logic: không traceback, chạy trót lọt, chỉ lộ ra khi bạn tự đặt đúng câu hỏi.
- Đếm số bạn đạt điểm ở mục 5 gãy vì cùng kiểu lỗi gán-lại-thay-vì-cộng-dồn — vòng bốn bước dùng lại được nguyên vẹn cho cả hai bug khác nhau.
10. Tự kiểm tra
- Q1Vì sao sửa mò — đổi một chỗ, chạy lại, thấy vẫn sai — không hội tụ dù thử bao nhiêu lần?
- Q2Câu "tôi nghĩ tong bị đặt lại về 0 ở mỗi vòng lặp" và câu "chắc vòng lặp có vấn đề" khác nhau ở điểm nào khiến một câu kiểm chứng được, câu kia không?
- Q3Ở ca mẫu tính tổng bốn bạn, vì sao đặt
print(tong)BÊN TRONG thân vòng lặp mới kiểm chứng được giả thuyết, còn đặt SAU vòng lặp như code gốc thì không? - Q4Khi nào nên chọn bảng trace thay vì print để kiểm chứng một giả thuyết?
- Q5Giả sử giả thuyết ban đầu của bạn SAI — kiểm chứng cho thấy tong vẫn cộng dồn đúng. Việc đó có phải một bước lùi không? Vì sao?
- Q6Vì sao lỗi
tong = 0đặt trong thân vòng lặp không có traceback nào để đọc, trong khi cadiem[3]ở bài trước lại có?
Bài tiếp theo: Kẹt là bình thường — quy tắc hai mươi phút
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên