Đây là khóa học đầu tiên về Khoa học Máy tính Đại học: nguyên lập lập trình
, được giảng dạy bằng Python 3. Khóa học không yêu cầu kiến thức lập trình trước đó. Every program in
mọi chương trình trong tài liệu này đều hoàn chỉnh và có thể chạy được — hãy gõ, chạy, thay đổi một dòng mã, và dự đoán
sự thay đổi trước khi chạy lại. Vòng lặp dự đoán rồi chạy chính là toàn bộ kỹ năng cốt lõi.
Khóa học được thiết kế như một thang:
Các đơn vị từ 1.1 đến 1.3 xây dựng nền tảng cơ bản: giá trị và kiểu dữ liệu, lựa chọn, lặp lại.
Đơn vị 1.4 đóng gói công việc vào các hàm.
Các đơn vị 1.5 và 1.6 bổ sung các tập hợp chuẩn và xử lý tệp tin cùng với khả năng xử lý lỗi.
Đơn vị 1.7 là kỷ luật cao hơn: phân rã bài toán, kiểm thử, gỡ lỗi.
Cách đọc từng đơn vị: một câu chuyện ngắn giải thích tại sao nó quan trọng; một hình ảnh hoặc quy tắc chung
xuất hiện trước bất kỳ cú pháp nào; sau đó là các quy tắc chính xác; tiếp theo là các ví dụ minh họa ngày càng khó,
mỗi ví dụ đều có phần theo dõi (trace) mà bạn có thể kiểm tra bằng cách chạy mã nguồn; sau đó là những sai lầm thực tế mà người học
thường mắc phải; cuối cùng là tóm tắt ba dòng. Mọi ví dụ ở đây đều có thể được kiểm chứng bằng một cách khác —
chạy trực tiếp. Đọc mã nguồn mà không chạy nó giống như đọc bản nhạc mà không
nghe thấy nó.
1.1
Giá trị, kiểu và nhập/xuất
Một ứng dụng mua sắm hiển thị "4.20" — đó là văn bản hay số? Nếu là văn bản, cộng hai
giá sẽ cho kết quả "4.203.50"; nếu là số, kết quả sẽ là 8.7. Điều gì xảy ra phụ thuộc vào
kiểu, và toàn bộ đơn vị 1.1 nhằm mục đích học cách nhận diện kiểu trước khi bạn
chạy bất kỳ đoạn mã nào.
Mỗi giá trị đều có một kiểu, và kiểu quyết định những gì bạn có thể làm với nó: int (số nguyên)
số), float (số thập phân), str (văn bản), bool (True/False). The type(...)
có thể xác định kiểu của mọi giá trị — hãy sử dụng nó tùy ý trong quá trình học tập.
age = 21 # int
price = 4.20 # float
name = "Ada" # str
open_ = True # bool
print(type(age)) # <class 'int'>
Biến là tên được gán cho một giá trị; phép gán age = 22 sẽ thay đổi liên kết tên —
nó không bao giờ chỉnh sửa giá trị cũ. Hình ảnh tư duy này đáng được ghi nhớ suốt cả khóa học:
Tên biến là nhãn dán trên các hộp; biểu thức đọc các hộp và tạo ra các hộp mới
Việc tính toán biểu thức giống như "mở các hộp, thực hiện phép tính, đóng một hộp mới": count * 3.50
với count = 6 sẽ mở hộp int chứa 6 và hộp float chứa 3.50, và quy tắc kết hợp của Python
sẽ nâng cấp loại int — kết quả thu được là float21.0, chứ không phải int 21.
Phép chuyển đổi phải được thực hiện rõ ràng: int("21") có thể thành 21, nhưng "4" + 2 chỉ là một TypeError — Python
từ chối đoán xem bạn có ý "42" hay 6. Hiển thị kết quả với print(...); đầu vào nhận chuỗi
input(...) để nhập dữ liệu, vốn luôn trả về một chuỗi ký tự, do đó các số cần phải được chuyển đổi:
Ví dụ minh họa 1 (máy thu ngân quán cà phê).count = int(input("Coffees? ")), sau đó
print("Total:", count * 3.50). Tại 6 ly cà phê, chương trình sẽ in ra Total: 21.0 — tức là .0
dấu phẩy là số thực, vì int * float được nâng lên thành số thực. Theo dõi loại dữ liệu nào được trả về
của một biểu thức là thói quen của đơn vị 1.1. Theo dõi theo trạng thái: input trả về
"6"; phép gán int(...) mở một khung mới chứa giá trị int 6; phép nhân
tạo ra một khung float mới 21.0; hệ thống print hiển thị nó.
Ví dụ giải chi tiết 2 (tai nạn điển hình đã được sửa). Một máy in băng rôn ghép nối đếm
vào một thông điệp:
Chạy mã và Python sẽ gây ra lỗi TypeError: can only concatenate str (not "int") to str —
quy tắc từ chối phỏng đoán. Cách sửa chữa yêu cầu chuyển đổi rõ ràng:
"hôm nay")print("Sold " + str(count) + " today")+ có nghĩa là cộng "hôm nay"), which inserts the spaces for you. The lesson to carry: + biểu thị phép cộng
đối với số, phép nối đối với chuỗi, và tuyệt đối không trộn lẫn âm thầm.
Lỗi thường gặp. Quên rằng input() luôn trả về chuỗi, do đó
input() * 2 lặp lại văn bản thay vì nhân đôi một con số. Viết 4.20 + 6 và
kỳ vọng đáp án dạng int — bất kỳ số thực nào trong biểu thức sẽ tạo ra số thực. Tin tưởng vào
0.1 + 0.2 == 0.3 (nó thực chất là False; làm tròn nhị phân — đơn vị 1.2 chỉ ra cách khắc phục).
biểu thức trộn lẫn các loại bằng cách nâng lên với số thực; các phép chuyển đổi là rõ ràng và
biểu thức trộn lẫn kiểu dữ liệu thông qua nâng cấp với số thực; các phép chuyển đổi phải được nêu rõ.
input() cần một.
Cổng thu vé công viên giải trí phải tính tiền khác nhau cho trẻ em, người lớn và người cao tuổi — một
chương trình, nhiều đường đi. Chọn lọc là cách chương trình lựa chọn: if, elif,
else chỉ chạy một khối khi một điều kiện đúng.
Tuổi 70, học sinh: nhánh đầu tiên trả về TRUE sẽ quyết định giá
Điều kiện có thể là bất kỳ biểu thức boolean nào; phép so sánh (==, !=, <, >=) và
logic (and, or, not) tạo nên nó. Hai quy tắc giúp mọi quyết định dễ đọc:
Các nhánh được kiểm tra từ trên xuống dưới; nhánh đầu tiên đúng sẽ chiến thắng và các
nhánh còn lại bị bỏ qua.
Indentation không phải trang trí — nó CHÍNH là khối lệnh. Một khoảng trắng thừa sẽ làm thay đổi
những dòng nào thuộc về nhánh đó.
temp = 37.5
if temp >= 38:
print("Fever")
elif temp >= 36 and temp < 38:
print("Normal")
else:
print("Low")
Ví dụ minh họa 1 (giá vé rạp chiếu phim). Dưới 12 hoặc 65+ phải trả 5, học sinh trả 7,
mọi người khác trả 10:
age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
price = 5
elif student:
price = 7
else:
price = 10
print("Pay", price)
Thứ tự rất quan trọng: nhánh đầu tiên đúng sẽ chiến thắng, chính vì vậy việc kiểm tra tuổi phải nằm trước
lần kiểm tra sinh viên — một sinh viên 70-tuổi nên trả 5, và thứ tự này nói lên điều đó. Chạy
thủ tục với 70 và y để xem đường đi bên trái nhất của sơ đồ luồng được thực thi.
Ví dụ minh họa 2 (ngưỡng điểm số, nâng cấp). Cấu trúc khung giống nhau, giờ đây là một chuỗi
trong đó mỗi elif chỉ cần một phép so sánh, bởi vì các phép thử ở phía trên đã thất bại rồi:
thất bại:
score = int(input("Score? "))
if score >= 80:
band = "A"
elif score >= 70:
band = "B"
elif score >= 60:
band = "C"
else:
band = "F"
print("Band:", band)
lưu ý: ⟨79⟩ không đồng thời cũng cần ⟨Band: B⟩ — việc đạt đến
lưu ý: không cần thêm and score < 80 vào elif score >= 70 — việc đạt đến dòng
này đã chứng minh điểm số thấp hơn 80. Tin tưởng vào cơ chế fall-through như vậy là
Lỗi gì khiến chuỗi bị ngắn; kiểm tra lại mọi thứ là cách của người mới, dài hơn và sai hơn.
phiên bản.
So sánh số thực cần cẩn trọng: 0.1 + 0.2 == 0.3 là False (làm tròn nhị phân,
làm tròn), nên hãy so sánh với độ dung sai: abs(x - y) < 1e-9.
Lỗi thường gặp. Viết = thay vì == — lỗi kinh điển, và Python
Tóm tắt: điều kiện chọn đường đi, nguyên tắc "đúng đầu tiên sẽ thắng", thụt lề xác định khối lệnh; sắp xếp bài kiểm tra từ cụ thể đến tổng quát; số thực so sánh theo độ cho phép, tuyệt đối không dùng SyntaxError.
đã bắt được nó.
Thanh toán hóa đơn elif bằng tay là cả một sự nghiệp; với vòng lặp thì đó là chương trình. Lặp
Tóm tắt: điều kiện chọn đường đi, đầu tiên đúng sẽ thắng, thụt lề là khối; thứ tự
Tóm tắt: điều kiện chọn đường đi, ưu tiên giá trị đúng đầu tiên, thụt lề xác định khối; thứ tự
== đều cần ba yếu tố hiển thị trong mã: giá trị khởi tạo của biến, điều kiện, và một hành động bên trong đưa biến tiến gần đến điểm thoát — nếu không sẽ là vòng lặp vô hạn.
1.3
Lặp: thực hiện lại
Thanh toán hóa đơn 5,000 bằng tay là một nghề; dùng vòng lặp thì đó là chương trình. Lặp
lặp lại công việc: vòng lặp for duyệt qua một dãy có kích thước xác định; vòng lặp while chạy trong khi có
một điều kiện quyết định?
điều kiện quyết định?
total = 0
for i in range(1, 101): # 1..100 — range stops BEFORE its end
total += i
print(total) # 5050
Dùng for khi biết trước số lần lặp; dùng while khi điều kiện quyết định. Mỗi
while cần ba yếu tố rõ ràng trong mã nguồn: giá trị khởi tạo của biến, điều kiện,
và một thứ gì đó bên trong di chuyển về phía thoát ra — nếu không thì nó là một vòng lặp vô hạn.
vòng lặp.
Bảng truy vết cho vòng lặp tính giai thừa
n = 5
factorial = 1
while n > 1:
factorial *= n
n -= 1 # the step toward exiting
print(factorial) # 120
Bảng truy vết là thói quen kiểm tra của khóa học đối với các vòng lặp: hãy theo dõi
các biến qua từng vòng, trước hết trên giấy. Đối với vòng lặp tính giai thừa:
(n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). Vẽ bảng bằng tay, sau đó
thêm một print(n, factorial) vào bên trong vòng lặp và để Python chấm điểm cho bạn.
Ví dụ minh họa 2 (một phép tìm kiếm với break, nâng cao). Tìm bội số đầu tiên của
7 lớn hơn 100 — số lần lặp chưa biết, điều kiện sẽ quyết định:
n = 100
while True:
if n % 7 == 0:
break # found it — leave the loop now
n += 1
print(n) # 105
Truy vết ngắn gọn: 100, 101, …, 104 đều không thỏa mãn thử nghiệm; 105 thỏa mãn và break
thoát ra. break rời vòng lặp sớm; continue bỏ qua để chuyển sang vòng tiếp theo. Cả hai đều là
công cụ đọc hiểu — một vòng lặp phải quét cho đến khi tìm thấy thứ gì đó, rồi dừng lại, được
hiểu rõ nhất với break. Sự nâng cao hơn nữa, nhân đôi tiền: ở mức 5% mỗi năm, bao lâu
thì 1000 mất để đạt đến 2000?
balance = 1000.0
years = 0
while balance < 2000.0: # the condition itself is the goal
balance *= 1.05
years += 1
print(years, round(balance, 2)) # 15 2078.93
Mười lăm năm, và vòng lặp dừng ngay lập tức khi điều kiện thay đổi — bảng truy vết
sẽ cho thấy số dư vượt qua ngưỡng tại 2078.93, chứ không chạm đúng vào 2000.
Lỗi thường gặp. Chỉnh sửa danh sách (đơn vị 1.5) hoặc biến điều khiển vòng lặp mà bạn đang
for-lặp qua. range(1, 100) khi thực chất ý là 100 — phần cuối bị loại trừ.
Một while mà bước thoát ra bị thiếu hoặc bị comment đi: điều kiện không bao giờ thay đổi,
và chương trình bị treo.
Tóm tắt: for cho số lần lặp đã biết, while cho điều kiện; mọi vòng lặp đều có điểm bắt đầu,
điều kiện và bước cập nhật; truy vết trên giấy, sau đó in ra để chấm; break và continue dùng cho
clean exits sớm.
1.4
Hàm: đặt tên cho một công việc chỉ một lần
Công thức tính giá giống nhau xuất hiện ở năm nơi; quy tắc thuế thay đổi; bốn trong số
năm được cập nhật. Một hàm đóng gói một công việc sau tên gọi để nó được viết
một lần và được tin cậy ở mọi nơi: tham số vào, giá trị trả về ra. def
xác định; gọi thực thi.
def total_price(count, unit=3.50):
"""Return the total for count items at unit price."""
return count * unit
print(total_price(6)) # 21.0
print(total_price(6, unit=4.0)) # 24.0
Định nghĩa cung cấp một tham số (count, unit); lời gọi cung cấp một
thông số đối ứng (6). Giá trị mặc định (unit=3.50) làm cho thông số đối ứng trở nên tùy chọn.
return trả lại một giá trị và kết thúc lời gọi; in không phải là trả về — một
hàm không có return sẽ trả về None, và người gọi cố thực hiện phép toán trên nó
sẽ nhận được một TypeError.
Lời gọi lồng nhau: người gọi tạm dừng cho đến khi giá trị quay lại
Mỗi lời gọi có phạm vi cục bộ riêng: các tên bên trong bị vô hiệu hóa
bên ngoài, và biến mất khi lời gọi kết thúc. Dấu vết lời gọi của các lời gọi lồng nhau đọc:
người gọi tạm dừng → người gọi chạy → giá trị trả về rơi vào người gọi.
Ví dụ minh họa 1 (đơn hàng bao gồm phí).
def add_fee(amount):
"""Return amount plus a fixed 3 pence fee."""
return amount + 3
def order_total(unit_pence, quantity):
"""Return the order total: subtotal plus the fixed fee."""
return add_fee(unit_pence * quantity)
print(order_total(250, 4)) # 1003
Hợp đồng trong docstring rất quan trọng: phần nguyên xu vào, phần nguyên xu ra. Theo dõi
order_total(250, 4): tổng phụ 1000, add_fee trả về 1003 — và sơ đồ khung
ở trên cho thấy sự tạm dừng và sự chuyển giao.
Ví dụ minh họa 2 (một thuật toán thực tế bên trong một hàm nhỏ, được mở rộng).is_prime(n) trả về True khi không có phần tử nào từ 2 đến $\sqrt{n}$ chia hết cho n:
def is_prime(n):
if n < 2:
return False
d = 2
while d * d <= n: # only test up to the square root
if n % d == 0:
return False # found a divisor — answer known, stop
d += 1
return True
print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Hai thói quen của hàm gặp nhau ở đây: một phép return sớm return sẽ kết thúc gọi hàm ngay khi câu trả lời
đã biết (không cần biến cờ), và giới hạn vòng lặp d * d <= n xuất phát từ
phân tích — nếu $n = a \times b$ với $a \le b$, thì $a \le \sqrt{n}$, do đó kiểm tra vượt quá $\sqrt{n}$
sẽ kiểm tra lại các ước số đã nhìn thấy. Chạy thử: danh sách thu được chính là các số nguyên tố dưới 30.
Lỗi thường gặp. In dữ liệu trực tiếp trong hàm thay vì sử dụng lệnh trả về, khiến kết quả
không thể tái sử dụng. Quên lệnh return return trên một nhánh — đường đi này âm thầm tạo ra
None. Truy cập vào các biến cục bộ của hàm từ bên ngoài; chúng bị xóa ngay khi hàm
kết thúc thực thi.
Tóm tắt: gọi def một lần, sử dụng ở bất kỳ đâu; tham số truyền vào, một giá trị trả ra qua return;
trả về sớm sẽ kết thúc hàm; biến cục bộ tồn tại và mất đi cùng chu kỳ gọi hàm; docstring mô tả hợp đồng.
hợp đồng.
Cấu trúc dữ liệu: danh sách, từ điển, tuple, tập hợp
Máy thu tiền của quán cà phê cần thứ tự hóa đơn trong ngày, thực đơn theo tên, vị trí cố định
không bị dịch chuyển, và các loại đồ uống độc nhất hôm nay. Bốn nhiệm vụ, bốn loại container — việc lựa chọn
giữa chúng là quyết định thiết kế của module 1.5.
Một danh sách là một dãy có thứ tự và có thể thay đổi: marks = [72, 85, 61], được đánh chỉ mục
bắt đầu từ 0, cắt slice với marks[1:3]. Một từ điển ánh xạ khóa sang giá trị:
price = {"flat white": 3.5, "tea": 2.8}, đọc và ghi dựa trên khóa. Một tuple
là một chuỗi cố định (53.1, -1.5) — phù hợp cho tọa độ không được thay đổi. A
set chứa các phần tử duy nhất không có thứ tự: {"a", "b"}.
marks = [72, 85, 61]
marks.append(90) # [72, 85, 61, 90]
best = max(marks) # 90
average = sum(marks) / len(marks) # 77.0
Aliasing: hai tên, một list
Gán giá trị không sao chép: a = [1]; b = a; b.append(2) khiến a trở thành [1, 2] — đó là list
được chia sẻ, các tên thì không. Aliasing này chính là nguyên tắc 1.1's, giờ
với hai nhãn trên một ô, và nó cũng giải thích quy tắc tiếp theo: thay đổi (mutate) một list trong
khi duyệt qua nó sẽ bỏ sót phần tử — hãy duyệt trên bản sao (for m in marks[:]) hoặc tạo
một list mới.
Khóa .get(key, 0) của dictionary cung cấp 0 cho lần xuất hiện đầu tiên — cách viết gọn
làm cho việc đếm chỉ còn một dòng lệnh. Kiểm tra bằng tay: "tea" xuất hiện ba lần trong orders,
và kết quả in ra xác nhận điều đó.
Ví dụ minh họa 2 (mặt hàng bán chạy nhất ngày, nâng cao). Tiếp tục từ ví dụ đếm,
báo cáo mặt hàng dẫn đầu và số lượng của nó:
best = max(tally, key=tally.get)
print(best, tally[best]) # tea 3
Tham số key=tally.get yêu cầu max so sánh các mục dựa trên số lượng thay vì
thứ tự bảng chữ cái — kỹ thuật key= tương tự có thể sắp xếp bất kỳ tập hợp nào theo tiêu chí tùy ý. Việc
nâng cấp này quan trọng: các chương trình thực tế thường kết nối các công cụ này (đếm trước, rồi xếp hạng) thay vì
quét lại orders hai lần.
Việc chọn loại cấu trúc dữ liệu là quyết định thiết kế: thứ tự + phần tử trùng → list; tra cứu theo
tên → từ điển; nhóm cố định → tuple; tính duy nhất → tập hợp.
Lỗi thường gặp.tally[item] += 1 trên một khóa chưa có trong từ điển
(KeyError) — .get là cách khắc phục. Ngại rằng tập hợp hoặc từ điển ghi nhớ thứ tự.
Giả định rằng b = a sao chép danh sách; thực tế nó trỏ đến cùng một đối tượng.
Tóm tắt: bốn bộ chứa cho bốn công việc; gán chia sẻ, không bao giờ sao chép; .get
giúp đếm an toàn; key= biến việc xếp hạng thành một dòng lệnh.
Cà phê đóng cửa, điện mất — và tổng số của buổi sáng chỉ tồn tại nếu chương trình
đã ghi nó vào ổ đĩa. Một tập tin tồn tại lâu hơn cả chương trình. with open(path) as f: opens, closes on exit, and reads text; open(path, "w") ghi
(và xóa trống trước — đó là sai lầm kinh điển).
with open("log.txt", "w") as f:
f.write("opened\n")
with open("log.txt") as f:
for line in f:
print(line.rstrip()) # rstrip removes the trailing newline
Khối with là lan can bảo vệ: bất kể điều gì xảy ra bên trong, tập tin đều được đóng lại.
Đọc dữ liệu trả về các dòng kèm ký tự xuống dòng, nên .rstrip() cần nằm trong mọi vòng lặp.
Lỗi là các giá trị Python phát ra dưới dạng ** ngoại lệ**: FileNotFoundError,
ValueError, KeyError, TypeError. try/except bắt một kiểu tên cụ thể — không bao giờ dùng
except: đơn thuần vì sẽ che giấu các lỗi thực sự.
Ví dụ minh họa 1 (tệp cài đặt bị thiếu). Đọc kích thước văn bản từ tệp cài đặt
có thể bị mất, hoặc có thể không chứa số nguyên:
try:
with open("settings.txt") as f:
size = int(f.read().strip())
except FileNotFoundError:
size = 12 # no file yet: a sensible default
except ValueError:
size = 12 # the file holds "big", not a number
print("Text size", size)
Chạy ba lần — với 16 trong tệp, không có tệp nào, và với big trong
tệp — và nó in ra kích thước sử dụng được mỗi lần, từ ba đường dẫn khác nhau. Đó chính là hình dáng của mã code bền vững: một cách giải quyết hợp lý
kết quả, có nhiều con đường dẫn đến nó. Xử lý lỗi bạn dự kiến, nơi bạn có thể phản hồi lại
nó; để phần còn lại bị sập mạnh mẽ.
Ví dụ được hướng dẫn 2 (vòng lặp đầu vào bền vững, được nâng cấp). Yêu cầu người dùng nhập một
số nguyên:
Dẫn nó qua x sau đó là 4: lần chạy đầu tiên gây ra, bắt được, in thông báo, và lần
chạy while True yêu cầu lại; lần chạy thứ hai của int(...) thành công và return thoát
khỏi vòng lặp và hàm chỉ trong một bước. Ba đơn vị kết hợp ở đây — vòng lặp (1.3),
hợp đồng hàm (1.4) và ngoại lệ (1.6) — đó chính là điểm mấu chốt của việc
nâng cấp.
Common mistakes.open(path, "w") on the wrong path: the file is created or
emptied before you notice. Catching Exception broadly and continuing with broken
Quên .rstrip(), khiến kết quả hiển thị các dòng cách nhau hai khoảng trắng.
Tóm tắt: Dùng with open(...) cho cả đọc và viết; "w" sẽ làm trống trước,
ngoại lệ là các giá trị — hãy bắt đúng loại tên mà bạn dự kiến, fallback hợp lý, để
phần còn lại bị sập.
Hai nhóm viết cùng một ứng dụng; một nhóm ra mắt trong một tuần, một nhóm chìm đắm trong tháng. Sự
khác biệt không phải là tốc độ gõ phím — mà là mã của nhóm nhanh được xây dựng từ những mảnh nhỏ
mà họ có thể chứng minh tính đúng đắn từng phần, trong khi nhóm chậm gỡ lỗi toàn bộ ứng dụng cùng lúc.
Đơn vị 1.7 chính là kỷ luật này.
Phân rã chia nhỏ một bài toán thành các hàm riêng biệt, mỗi hàm thực hiện đúng một nhiệm vụ — đủ nhỏ để có thể tự kiểm tra độc lập.
Kiểm thử so sánh kết quả đầu ra thực tế của một hàm với giá trị mong đợi, đặc biệt tại các biên: không, một, rỗng, âm, và cực lớn.
kỳ vọng, tại các ranh giới: không, một, rỗng, âm, rất lớn.
An assert that never fires is a passing test. Write the tests before you write the
(đỏ → xanh → tinh chỉnh) chính là thói quen đó đang vận hành liên tục.
(đỏ → xanh → tinh chỉnh) chính là thói quen vận hành liên tục.
Ví dụ minh họa 1 (mở rộng bài kiểm tra bằng hàm). Mở rộng order_total với
10% giảm giá trên £10 — bài kiểm tra được viết trước, tại biên độ mới:
(1003) sửa một vấn đề; (902.7) chạy lại các bài kiểm thử. Di chuyển lệnh in về giữa đoạn mã sau mỗi lần chạy sẽ thu hẹp khoảng tìm kiếm đi một nửa: bốn lệnh in có thể phát hiện lỗi trong chương trình dài mười sáu dòng, dù
int(...) làm tròn xuống thành 902. Lưu ý asserts đầu tiên: tại 200 pence và bốn
tách, tổng vẫn giữ nguyên 803 — các bài kiểm tra biên chứng minh cả việc giảm giá
được kích hoạt và không bị kích hoạt quá sớm.
Ví dụ minh họa 2 (cắt đôi một con bọ bằng dấu chân, được nâng cao). Tổng số từ 1.5
lệnh in là {'tea': 3} nhưng dòng bán chạy nhất lại in sai loại đồ uống. Phương pháp:
(1) tái tạo với bộ đầu vào thất bại nhỏ nhất; (2) in các giá trị tại biên
giữa phần tin cậy và phần nghi ngờ — ở đây, tổng trước khi ⟨max⟩;
(3) sửa một thứ gì đó; (4) chạy lại bài kiểm tra. Di chuyển lệnh in giữa chừng trong mã
mỗi lần chạy sẽ thu hẹp một nửa vùng tìm kiếm: bốn lệnh in có thể phát hiện lỗi trong chương trình gồm mười sáu dòng, tuy nhiên
nó ẩn đi.
Chẩn đoán lỗi là tìm ra sự khác biệt giữa những gì bạn cho rằng mã thực hiện
và những gì nó thực hiện — dòng in tại ranh giới tin cậy/nghi ngờ chính là công cụ.
Những sai lầm phổ biến. Kiểm thử chỉ đường dẫn thành công: các ranh giới (không, một, rỗng,
âm) là nơi chương trình bị hỏng. Viết một tuần code trước khi có kiểm thử đầu tiên,
sau đó phải chẩn đoán lỗi như núi cao. Sửa hai thứ cùng lúc, nên kết quả của cả hai đều không có ý nghĩa
điều gì cả.
Tóm tắt: phân tách cho đến khi mỗi phần có thể kiểm thử độc lập; khẳng định các ranh giới ngay khi một
hàm tồn tại; viết kiểm thử trước khi có thể; chẩn đoán lỗi bằng phương pháp chia đôi với các dòng in
tại ranh giới tin cậy — thói quen mà toàn bộ chương trình học dựa vào.
Bài học tương tác về chủ đề này
Làm theo từng bước, kèm theo bài tập kiểm tra ngay lập tức.