Bảng trace — chạy chương trình bằng tay
Công cụ xương sống của khoá: cái bảng để bạn đóng vai cỗ máy, đi từng dòng và ghi lại mọi biến. Trace được thì viết được, không thì đoán mò.
TL;DR: Bảng trace là công cụ để bạn đóng vai máy: kẻ một bảng có cột là từng biến, hàng là từng dòng lệnh, rồi sau mỗi dòng chạy xong bạn ghi lại giá trị mới nhất của mọi biến. Một nghiên cứu đối chứng trên 24 sinh viên năm nhất cho thấy chỉ cần dạy tường minh cách viết bảng này 5-10 phút, điểm trace tăng 15% và độ lệch giữa các bài giảm gần một nửa — vì sinh viên trace kém thường chỉ nghĩ tới việc viết ra giấy chứ không thực sự làm, rồi bỏ cuộc giữa chừng. Bài này cho bạn một đoạn tám dòng lệnh tuần tự, điền sẵn hai hàng đầu, và bắt bạn tự cầm giấy điền nốt trước khi mở đáp án.
Nếu ai đó đưa bạn một đoạn tám dòng code rồi hỏi "sau dòng cuối, biến này bằng bao nhiêu?" — bạn có tự tin trả lời đúng, hay phải đọc đi đọc lại và vẫn đoán?
Bài này cho bạn công cụ để không phải đoán: bảng trace. Xong bài, bạn tự chạy được một đoạn tám dòng lệnh tuần tự bằng tay, không cần máy tính.
1. Vì sao trace bằng tay, không phải đọc lướt
Một nghiên cứu đối chứng năm 2018 chia 24 sinh viên nhập môn thành hai nhóm. Nhóm được dạy đúng một kỹ thuật trong 5-10 phút (đọc code từng dòng, kẻ bảng ghi lại giá trị biến sau mỗi dòng) đạt điểm trace cao hơn nhóm còn lại 15%, độ lệch giữa các bài làm thấp hơn 46%.
Chi tiết đáng chú ý nhất nằm ở cách hai nhóm hành xử: sinh viên trace giỏi nhất tự động vẽ bảng cột khi gặp code nhiều biến, không ai bắt. Sinh viên trace kém nhất thừa nhận có nghĩ tới việc viết bảng ra giấy — nhưng không làm, rồi bỏ cuộc giữa chừng khi code phức tạp lên. Bảng trace là ranh giới giữa "trace được" và "đoán rồi bỏ cuộc", và ranh giới đó nằm ở đúng một hành động: có thực sự cầm bút kẻ bảng hay không.
2. Bảng trace là gì?
Bạn vẫn ghi sổ chi tiêu cá nhân mỗi ngày, đúng không? Mỗi lần có giao dịch (mua đồ, nhận lương), bạn viết một dòng mới: số dư tài khoản SAU giao dịch đó là bao nhiêu. Bạn không xoá dòng cũ, nhưng số dư đúng để dùng luôn là dòng mới nhất. Muốn biết hiện còn bao nhiêu tiền, bạn không cộng lại từ đầu — bạn nhìn dòng cuối.
Bảng trace hoạt động y hệt, chỉ khác chỗ "giao dịch" là một dòng lệnh, và "tài khoản" là một biến:
| Sổ chi tiêu | Bảng trace |
|---|---|
| Mỗi dòng sổ = một giao dịch | Mỗi hàng bảng = một dòng lệnh vừa chạy xong |
| Mỗi cột số dư = một tài khoản | Mỗi cột = một biến |
| Số dư dòng mới nhất = số tiền hiện có | Giá trị ở hàng mới nhất = giá trị hiện tại của biến |
| Chưa mở tài khoản thì chưa có số dư | Biến chưa được gán lần nào thì ô để trống |
Cột là biến, hàng là dòng lệnh. Đọc một hàng theo chiều ngang, bạn thấy trạng thái cả chương trình ngay sau dòng đó. Đọc một cột theo chiều dọc, bạn thấy lịch sử của một biến qua thời gian.
3. Dựng bảng — worked example
Phép gán chạy theo một thứ tự cố định: máy tính xong toàn bộ vế phải trước, ra một giá trị duy nhất, rồi mới chép giá trị đó vào ô nhớ bên trái. Bạn áp dụng đúng quy tắc này ở từng hàng bảng trace bên dưới.
Đoạn code sau tính tiền vé xem phim cho một nhóm bạn — họ đổi ý mua thêm vé, có mã giảm giá, rồi phát hiện suất chiếu họ chọn tính giá vé cao hơn:
# 1. Tinh tien cho nhom ban dau
ve = 45000
sl = 2
tong = ve * sl
# 2. Doi y, mua them 1 ve
sl = sl + 1
tong = ve * sl
# 3. Ap ma giam gia
tong = tong - 15000
# 4. Suat chieu doi, gia ve tang
ve = 50000
tong = ve * sl
Tám dòng lệnh thật sự chạy (comment không tính), chia bốn nhóm theo việc chúng làm — mỗi nhóm là một bước tính có nghĩa.
Bảng trace có 3 cột biến: ve, sl, tong. Hai hàng đầu điền sẵn để bạn thấy quy trình:
| Dòng | Lệnh | ve | sl | tong |
|---|---|---|---|---|
| 1 | ve = 45000 | 45000 | – | – |
| 2 | sl = 2 | 45000 | 2 | – |
| 3 | tong = ve * sl | … | … | … |
| 4 | sl = sl + 1 | … | … | … |
| 5 | tong = ve * sl | … | … | … |
| 6 | tong = tong - 15000 | … | … | … |
| 7 | ve = 50000 | … | … | … |
| 8 | tong = ve * sl | … | … | … |
Hai điều để ý ở hai hàng mẫu. Thứ nhất, ô "–" ở hàng 1 (biến sl, tong chưa hề xuất hiện trong code) nghĩa là "chưa có giá trị nào", KHÔNG phải "0": biến chưa được gán lần nào thì chưa tồn tại, giống tài khoản chưa mở thì chưa có số dư. Thứ hai, mỗi hàng lặp lại TOÀN BỘ giá trị hiện tại của mọi biến, kể cả biến dòng đó không đụng tới. Hàng 2 vẫn ghi ve = 45000 dù dòng 2 chỉ gán sl, vì bảng trace trả lời "trạng thái cả chương trình lúc này", không chỉ "dòng vừa rồi làm gì".
4. Tới lượt bạn — điền tiếp bảng
Kẻ một bảng 5 cột (Dòng, Lệnh, ve, sl, tong) trên giấy. Chép lại hai hàng đầu y như ở mục 3. Tự chạy tay từng dòng 3 đến 8: đọc lệnh, tính vế phải bằng giá trị bạn VỪA ghi ở hàng trước, gán kết quả vào đúng cột, viết một hàng mới. Làm xong cả 8 hàng trên giấy rồi mới đọc tiếp — đáp án nằm ở mục 10 "Đáp án", ngay trước phần tự kiểm tra.
Bắt buộc, không tuỳ chọn: chỉ đọc lướt xuống mà không cầm bút là bạn đang rơi đúng vào nhóm sinh viên trace kém ở mục 1.
5. Cơ chế: máy chỉ đứng ở đúng một dòng
Máy thi hành đúng từng dòng theo thứ tự viết, không suy diễn ý định và không nhảy cóc. Đây là lý do bảng trace luôn đọc từ trên xuống, không bao giờ đọc theo thứ tự khác.
Sai lầm phổ biến khi mới trace là tưởng tượng nhiều dòng "chạy cùng lúc" như một hệ phương trình toán học. Máy không làm vậy. Tại mọi thời điểm, có đúng một dòng đang chạy, và dòng kế tiếp chỉ bắt đầu khi dòng hiện tại đã xong:

Con trỏ dòng hiện tại (vị trí máy "đang đứng") chỉ ở một node duy nhất tại một thời điểm, chỉ nhích xuống khi node đó chạy xong. Mỗi hàng bảng trace là "trạng thái SAU khi dòng đó chạy xong": bạn chụp ảnh vị trí con trỏ tại từng khoảnh khắc, không chụp cả chương trình cùng lúc. Thứ tự dòng vì thế quyết định kết quả: đổi chỗ dòng 7 lên trước dòng 3 thì mọi tong từ dòng 3 trở đi sẽ khác hẳn, vì dòng 3 lúc đó dùng ve = 50000 thay vì 45000.
6. Bẫy thường gặp khi trace
❌ Nhầm 1 — coi biến chưa gán là 0: quên rằng ô "–" ở hàng 1 nghĩa là sl chưa tồn tại, rồi tự ý coi nó bằng 0. Code thật đọc một biến trước khi gán nó lần nào sẽ dừng với NameError, không âm thầm coi là 0.
✅ Ô "–" nghĩa là "chưa có giá trị nào để dùng". Dòng nào đọc biến còn đang ghi "–" là dấu hiệu code có vấn đề, không phải bạn tính sai bảng.
❌ Nhầm 2 — giữ giá trị cũ sau khi biến bị ghi đè: người mới trace hay "neo" vào ve = 45000 vì đó là con số đọc đầu tiên, rồi dùng nhầm nó ở dòng 8 dù dòng 7 đã ghi đè thành 50000 — đúng lỗi hai hàng 4 và 7 ở mục 4 được thiết kế để bẫy.
✅ Mỗi hàng LUÔN ghi giá trị mới nhất, không phải giá trị "đáng nhớ". Trước khi điền một ô, hỏi: "dòng NÀY chạy xong thì biến này bằng bao nhiêu?".
7. 📚 Đào sâu — nghiên cứu về kỹ năng trace
An Explicit Strategy to Scaffold Novice Program Tracing (Xie, Nelson, Ko — SIGCSE 2018) — nguồn của con số ở mục 1, kèm mô tả cách dạy "line-by-line + bảng ô nhớ" trong 5-10 phút. Nghiên cứu nói về kỹ năng trace nói chung, không riêng Python — quy tắc "cột là biến, hàng là dòng lệnh" ở bài này là một cách hiện thực hoá cụ thể ý tưởng đó.
8. Liên hệ các bài khác
- Bài 1 — Chương trình là dãy lệnh — nền tảng cho mục 5: máy chạy đúng từng dòng theo thứ tự.
- Bài 2 — Biến và phép gán — quy tắc "tính vế phải trước, gán sau" bạn áp dụng ở mỗi hàng bảng trace.
- Bài 3 — Chuỗi và số — code có cộng chuỗi với số thì dùng cùng cách đọc traceback đã học ở đó.
- Bài 5 — Mini challenge: hoán đổi — nhiều sinh viên năm nhất giải thích sai một đoạn ba dòng gán tưởng đơn giản; công cụ vừa luyện ở đây dùng lại ngay.
9. Tóm tắt
- Quy trình một hàng: đọc dòng lệnh, tính vế phải bằng giá trị ĐANG có, gán kết quả vào cột đúng, viết hàng mới — không sửa đè lên hàng cũ.
- Thấy mình "đọc lướt là hiểu rồi" mà chưa cầm bút? Đó đúng là dấu hiệu người trace kém ở mục 1 — dừng lại, kẻ bảng trước khi code phức tạp hơn, đừng đợi tới lúc thấy khó.
- Phân vân điền "0" hay để trống cho một biến vừa nhắc tới? Kiểm tra lại: dòng nào phía trên đã gán nó chưa — chưa thì ghi "–", đừng đoán 0.
- Kết quả trace lệch với dự đoán? Đọc lại đúng THỨ TỰ dòng bạn vừa chạy — đổi chỗ hai dòng gán luôn đổi giá trị cuối, máy không tính trước rồi sắp xếp lại.
- Bẫy hay gặp nhất: quên cập nhật ô khi biến bị ghi đè.
10. Đáp án — bảng trace đầy đủ
Đối chiếu bảng bạn tự điền ở mục 4 với bảng dưới đây — chỉ mở sau khi đã tự làm xong cả 8 hàng trên giấy.
| Dòng | Lệnh | ve | sl | tong |
|---|---|---|---|---|
| 1 | ve = 45000 | 45000 | – | – |
| 2 | sl = 2 | 45000 | 2 | – |
| 3 | tong = ve * sl | 45000 | 2 | 90000 |
| 4 | sl = sl + 1 | 45000 | 3 | 90000 |
| 5 | tong = ve * sl | 45000 | 3 | 135000 |
| 6 | tong = tong - 15000 | 45000 | 3 | 120000 |
| 7 | ve = 50000 | 50000 | 3 | 120000 |
| 8 | tong = ve * sl | 50000 | 3 | 150000 |
Hai chỗ dễ sai nhất: dòng 4 tính sl + 1 bằng giá trị sl CŨ (2, từ hàng 2), không phải giá trị bạn vừa đoán; và dòng 7 ghi đè ve từ 45000 thành 50000 — giá trị 45000 mất hẳn, không dòng nào sau lấy lại được.
Nếu bảng bạn tự điền khớp từng ô — kể cả hai dấu "–" ở hàng 1 — bạn đã trace đúng. Lệch ở dòng 4 hoặc dòng 7, đọc lại mục 5 trước khi sang bài tiếp theo.
11. Tự kiểm tra
- Q1Vì sao ô của biến sl và tong ở hàng 1 của bảng trace ghi dấu "–" thay vì "0"?
- Q2Ở dòng 4 (sl = sl + 1), giá trị 2 của sl từ hàng 2 còn dùng lại được không sau khi dòng 4 chạy xong?
- Q3Nếu bạn chỉ đọc lướt đoạn code ở mục 3 mà không kẻ bảng, lỗi nhiều khả năng nhất xảy ra ở đâu, vì sao?
- Q4Nếu đổi chỗ dòng 7 (ve = 50000) lên ngay trước dòng 3, giá trị cuối của tong có thay đổi không? Giải thích bằng cơ chế con trỏ dòng hiện tại.
- Q5Bảng trace giúp bạn phát hiện loại lỗi gì mà chỉ đọc code từ trên xuống một lần thường bỏ sót?
- Q6Vì sao nghiên cứu ở mục 1 nói sinh viên trace kém "có nghĩ tới" việc viết bảng ra giấy nhưng vẫn không làm? Bài học cho chính bạn khi tự học là gì?
Bài tiếp theo: Mini challenge: hoán đổi hai biến
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