Nhập môn Tư duy Lập trình/Ca biên — rỗng, một phần tử, số 0, số âm, trùng
22/31
Bài 22 / 31~13 phútTừ đề bài tới thuật toánMiễn phí lượt xem

Ca biên — rỗng, một phần tử, số 0, số âm, trùng

Lời giải chạy đúng với ca thường rồi gãy với danh sách rỗng. Năm ca biên này gây phần lớn lỗi của người mới, và cả năm đều soi ra được trước khi chạy dòng nào.

TL;DR: Lời giải tìm điểm cao nhất chạy đúng với [7, 9, 5] không nói gì về việc nó có chạy đúng với mọi danh sách hay không. Bài này soi một đoạn code qua năm ca: danh sách rỗng làm nó gãy ngay dòng thứ hai, một phần tử và hai điểm trùng thì chạy đúng, có số 0 cũng đúng nhưng chỉ vì code tránh được một bẫy khởi tạo, còn số âm thì đề cấm mà code chẳng chặn. Cả năm ca đều đọc ra được từ ô Vào, trước khi chạy dòng nào.

Đoạn code tìm điểm cao nhất trong [7, 9, 5] chạy ra đúng một con số: 9. Bạn thử lại với [3, 8, 1], vẫn đúng. Bạn tin nó đã xong việc.

Nó chưa xong. Bạn mới thử những con số bình thường — không rỗng, không âm, không trùng nhau. Đề bài không hứa điều đó với mọi lần chạy sau; chỉ bạn tự hứa với chính mình khi chọn số để thử tay.

Lỗi lệch một đơn vị chính là ca biên đầu tiên bạn đã gặp, chỉ chưa ai gọi tên nó. Ca biên không mới — bạn dính nó rồi, chỉ chưa ai gọi tên cho nó thôi.

Bài này lo đúng một việc: soi năm ca dữ liệu mà người mới hầu như không tự nghĩ ra để thử, trên đúng đoạn code bạn đã tin là đúng.

1. Analogy — biển tải trọng ở đầu cầu

Bạn không cần lái xe tải qua cầu để biết cầu có sập hay không. Đầu cầu có sẵn tấm biển ghi giới hạn — "tối đa 8 tấn" — kỹ sư đã tính con số đó từ lúc thiết kế, không đợi cầu sập thật mới biết.

Ràng buộc trong ô Vào đóng đúng vai tấm biển đó — nói trước, ngay khi bạn đọc đề, dữ liệu nào an toàn và dữ liệu nào không, không đợi bạn chạy thử rồi mới báo lỗi.

Đầu cầuĐề bài
Biển ghi tải trọng tối đaRàng buộc ghi trong ô Vào
Đọc biển trước khi lái quaSoi ca biên trước khi chạy dòng nào
Xe quá tải làm cầu sậpDữ liệu ca biên làm code gãy hoặc trả lời sai
Kỹ sư tính giới hạn từ lúc thiết kế, không đợi cầu sậpBạn đọc ra ca biên từ lúc đọc đề, không đợi máy báo lỗi
Cầu này giới hạn 8 tấn, cầu khác giới hạn 30 tấnMỗi đề bài có tập ca biên riêng — rỗng, 0, âm, trùng chỉ là năm loại phổ biến nhất
💡 Cách nhớ

Biển báo không đợi tai nạn xảy ra mới treo lên. Ca biên cũng vậy — đọc ra được trước khi chạy dòng đầu tiên.

2. Lời giải cũ, năm câu hỏi mới

Đây là lời giải cho chính cái đề bạn đã bóc thành ba ô ở bài 1 — tìm điểm cao nhất — và nó chạy đúng với [7, 9, 5]:

diem = [7, 9, 5]
cao_nhat = diem[0]

for d in diem:
    if d > cao_nhat:
        cao_nhat = d

print(cao_nhat)

Output thật:

9

Đúng đoạn code này, không đổi một dòng nào, năm câu hỏi khác nhau đặt ra khi diem là:

  • danh sách rỗng
  • đúng một phần tử
  • danh sách có số 0
  • danh sách có số âm
  • hai bạn trùng điểm cao nhất

3. Ca biên đọc ra được từ đâu, nếu không phải chạy thử?

Từ chính ô Vào. Đề của lời giải trên chỉ nói "cho một danh sách điểm, tìm điểm cao nhất" — và câu đó im lặng về ba thứ:

  • Danh sách có bắt buộc khác rỗng không? Đề không nói.
  • Điểm số có bắt buộc lớn hơn 0 không? Đề không nói (hoặc nói mà code không kiểm tra).
  • Nếu hai người cùng đạt điểm cao nhất thì kết quả tính sao? Đề không nói.

Mỗi chỗ đề "không nói" chính là một ca biên. Không cần chạy máy để phát hiện ra chúng — chỉ cần đọc lại ô Vào và tự hỏi: giá trị nhỏ nhất mà ô này còn chấp nhận là gì, và giá trị ngay sát bên ngoài giới hạn đó thì sao?

4. Thử đoán — năm ca, một đoạn code

🖊️ Thử đoán

Với đúng đoạn code ở mục 2, không sửa gì cả, đoán xem chuyện gì xảy ra khi diem lần lượt là: [], [8], [0, 6, 2], [7, -3, 9], [9, 5, 9]. Với mỗi trường hợp, viết ra: chạy đúng, chạy sai, hay dừng chương trình vì lỗi — và vì sao.

Kết quả

5. Tới lượt bạn

Cùng kỹ năng đọc ô Vào trước khi chạy, áp cho một hàm khác: tính điểm trung bình của một lớp.

🐛 Tìm bug trong code này
diem = []
tong = 0

for d in diem:
  tong = tong + d

trung_binh = tong / len(diem)
print(trung_binh)

6. Bẫy thường gặp

Nhầm 1 — thử vài ca "bình thường" rồi coi như đã kiểm tra đủ. [7, 9, 5][3, 8, 1] đều là dữ liệu tự nghĩ ra, và dữ liệu tự nghĩ ra gần như luôn "đẹp": không rỗng, không âm, không trùng.

✅ Quay lại ô Vào và hỏi riêng cho từng ràng buộc: giá trị nhỏ nhất hợp lệ là gì, và giá trị ngay sát bên ngoài đó thì sao.

Nhầm 2 — khởi tạo biến lớn nhất bằng 0 cho "chắc ăn". Nhìn có vẻ an toàn vì 0 là số nhỏ, nhưng nó âm thầm giả định điểm cao nhất luôn lớn hơn 0.

✅ Khởi tạo mốc bằng chính một phần tử thật của dữ liệu — diem[0] — không phải một hằng số nghĩ sẵn.

Nhầm 3 — tin rằng đề bài cấm là code cũng cấm. Đề nói "điểm không âm" không có nghĩa dòng code nào đó tự động từ chối số âm.

✅ Ràng buộc trên giấy chỉ có hiệu lực khi có một dòng if thật sự kiểm tra nó.

7. 📚 Đào sâu — vì sao ca biên hay bị bỏ sót

📚 Đào sâu (không bắt buộc)

Lý do không nằm ở việc bạn lười, mà ở cách con người chọn ví dụ. Khi cần một con số để thử tay, bộ não gần như luôn chọn một con số "điển hình" — không to, không nhỏ, không đặc biệt. Kỹ thuật kiểm thử gọi nhóm dữ liệu đặc biệt này là giá trị biên (boundary value) — đúng ranh giới của một ràng buộc, hoặc ngay sát bên ngoài nó — và chúng gần như không bao giờ xuất hiện tự nhiên trong đầu người thử tay, vì theo định nghĩa chúng không "điển hình".

Cách chống là đọc lại từng ràng buộc trong ô Vào và hỏi đúng một cặp câu: giá trị nhỏ nhất còn hợp lệ là gì, và giá trị ngay sát bên ngoài — không còn hợp lệ nữa — là gì. "Danh sách điểm" kéo ra câu hỏi về số lượng tối thiểu; "điểm số" kéo ra câu hỏi về 0 và số âm.

8. Liên hệ các bài khác

9. Tóm tắt

  • Trước khi tin một lời giải đã "chạy đúng", tự hỏi mình đã thử ca nào ngoài dữ liệu đẹp chưa — nếu chưa, bạn mới chỉ chạy một nửa phép thử.
  • Danh sách rỗng làm lời giải ở mục 2 gãy ngay dòng khởi tạo cao_nhat = diem[0]; cùng ca đó làm hàm trung bình ở mục 5 gãy ở phép chia, vì rỗng lộ ra khác nhau tuỳ thuật toán dùng danh sách vào việc gì.
  • Mốc so sánh phải lấy từ chính dữ liệu, không phải từ một hằng số nghĩ sẵn — cao_nhat = diem[0] sống sót với điểm âm, cao_nhat = 0 thì không.
  • Ràng buộc chỉ tồn tại trên giấy cho tới khi có một dòng if kiểm tra nó; đọc lại ô VÀO rồi tìm xem dòng đó nằm ở đâu trong code.
  • Một phần tử và điểm trùng chạy đúng với lời giải này, nhưng "đúng" chỉ biết được sau khi soi, không phải mặc định.

10. Tự kiểm tra

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    Vì sao lời giải tìm điểm cao nhất chạy đúng với [7, 9, 5] không đủ để kết luận nó đúng với mọi danh sách?
  2. Q2
    Với đoạn code ở mục 2, vì sao diem = [] làm chương trình dừng bằng lỗi, chứ không chỉ đơn giản in ra rỗng hay 0?
  3. Q3
    Vì sao khởi tạo biến lớn nhất bằng diem[0] an toàn hơn khởi tạo bằng hằng số 0?
  4. Q4
    Đề bài cấm điểm số âm. Vì sao đoạn code ở mục 2 vẫn nhận và xử lý được diem = [7, -3, 9]?
  5. Q5
    Hai bạn cùng đạt điểm cao nhất có làm đoạn code ở mục 2 chạy sai không? Vì sao?
  6. Q6
    Hàm tính điểm trung bình ở mục 5 gãy vì cùng lý do gì với lời giải tìm điểm cao nhất ở ca rỗng, và khác nhau ở chỗ nào?

Bài tiếp theo: Mini challenge — từ đề tới lưu đồ hoàn chỉnh

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

Đặt 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

Bài tiếp theo

Mini challenge — từ đề tới lưu đồ hoàn chỉnh