Bỏ qua nội dung

Cơ sở lập trình

Khoa học Máy tính Đại học · Chủ đề 1

1.1

Khóa học này là gì và cách sử dụng nó

Đâ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:

Trạng thái chương trình dưới dạng ô: mỗi nhãn tên trỏ vào ô giá trị riêng của nó; việc gắn lại di chuyển nhãn, không bao giờ chỉnh sửa ô. Biểu thức 6 * 3.50 đọc hai ô và tạo ra một ô float mới 21.0
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à float 21.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:

born = int(input("Birth year? "))
print("About", 2026 - born, "years old")

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:

count = int(input("Tickets? "))
print("Sold " + count + " today")

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.

Từ vựng Luyện tập
English Tiếng Việt
programming fundamentals/ˈprəʊɡræmɪŋ ˌfʌndəˈmentlz/ nguyên lý lập trình cơ bản
type/taɪp/ Kiểu
set/set/ tập hợp
1.2

Lựa chọn: chọn giữa các đường đi

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.

Sơ đồ luồng của quy tắc giá vé phim ảnh với việc kiểm tra tuổi trước khi kiểm tra sinh viên; một sinh viên 70 tuổi sẽ nhận nhánh đúng đầu tiên và trả 5
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:

  1. 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.
  2. 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.

Vòng lặp while tính giai thừa bên cạnh bảng theo dõi của nó: n và factorial sau mỗi vòng, từ 5 và 1 đến 1 và 120, khi điều kiện trở thành False
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.

Ba khung gọi: print gọi order_total(250, 4), tạm dừng khi add_fee(1000) chạy; 1003 được trả lại dọc theo chuỗi
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.

Từ vựng Luyện tập
English Tiếng Việt
string/strɪŋ/ Chuỗi
selection/sɪˈlekʃn/ Lựa chọn
iteration/ˌɪtəˈreɪʃn/ lặp lại
infinite loop/ˈɪnfɪnət luːp/ Vòng lặp vô hạn
trace table/treɪs ˈteɪbl/ Bảng theo dõi
function/ˈfʌŋkʃn/ Hàm
return value/rɪˈtɜːn ˈvæljuː/ giá trị trả về
parameter/pəˈræmɪtə/ tham số
argument/ˈɑːɡjuːmənt/ tham số thực
local scope/ˈləʊkl skəʊp/ phạm vi cục bộ
1.5

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
Tên a và b đều trỏ đến cùng một đối tượng list, chứa 1 và 2 sau khi thực hiện b.append(2)
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.

Ví dụ minh họa 1 (đếm theo nhóm).

orders = ["tea", "flat white", "tea", "tea", "mocha"]
tally = {}
for item in orders:
    tally[item] = tally.get(item, 0) + 1
print(tally)   # {'tea': 3, 'flat white': 1, 'mocha': 1}

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.

Từ vựng Luyện tập
English Tiếng Việt
list/lɪst/ danh sách
dictionary/ˈdɪkʃənəri/ từ điển
tuple/ˈtuːpl/ cặp có thứ tự
aliasing/ˈeɪlɪəsɪŋ/ tên gọi chung
1.6

Tập tin và lỗi

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ẽ.

Người đọc cài đặt: khối try mở, đọc và chuyển đổi giá trị; nó thành công, hoặc nhánh FileNotFoundError hoặc ValueError thiết lập kích thước mặc định 12; cả ba con đường đều gặp nhau và chương trình tiếp tục

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:

def read_count():
    while True:
        try:
            return int(input("How many? "))
        except ValueError:
            print("Whole numbers only.")

print(read_count())

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.

Từ vựng Luyện tập
English Tiếng Việt
value/ˈvæljuː/ Giá trị
variables/ˈveərɪəblz/ biến số
file/faɪl/ tệp tin
exceptions ngoại lệ
decomposition/ˌdiːkɒmpəˈzɪʃn/ sự phân rã
testing/ˈtestɪŋ/ kiểm thử
debugging/ˈdiːbʌɡɪŋ/ gỡ lỗi
1.7

Phân rã, kiểm thử và gỡ lỗi

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.

Vòng lặp dẫn dắt bởi kiểm thử: viết một kiểm thử thất bại để mô tả hợp đồng; chạy nó và xem kết quả thất bại do đúng lý do; viết thân code đơn giản nhất để vượt qua; chạy lại cho đến khi thành công; tinh chỉnh khi các kiểm thử vẫn giữ nguyên cấu trúc
Vòng lặp dẫn dắt bởi kiểm thử: đỏ → xanh → tinh chỉnh
def add_fee(amount):
    return amount + 3

def order_total(unit_pence, quantity):
    return add_fee(unit_pence * quantity)

assert order_total(250, 4) == 1003
assert order_total(250, 0) == 3      # the zero-quantity boundary
assert order_total(0, 4) == 3

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:

def order_total(unit_pence, quantity):
    subtotal = add_fee(unit_pence * quantity)
    if subtotal >= 1000:             # pence: over £10
        subtotal = int(subtotal * 0.9)
    return subtotal

assert order_total(200, 4) == 803    # under £10 — unchanged
assert order_total(250, 4) == 902    # 1003 pence → 10% off → 902.7 → 902
assert order_total(250, 5) == 1127   # 1253 pence → 1127.7 → 1127

(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.

Nhiều chủ đề hơn trong Khoa học Máy tính Đại học

Đăng nhập hoặc tạo tài khoản

IGCSE, A-Level & AP