Nhập môn Tư duy Lập trình/Hàm — đặt tên cho một nhóm bước, nhận vào gì trả ra gì
21/31
Bài 21 / 31~13 phútTừ đề bài tới thuật toánMiễn phí lượt xem

Hàm — đặt tên cho một nhóm bước, nhận vào gì trả ra gì

Hàm không phải cú pháp mới phải nhớ, nó là hiện thân của việc phân rã bạn vừa làm: một nhóm bước xứng đáng có tên, nhận vào cái gì và trả ra cái gì.

TL;DR: Hàm không phải cú pháp mới phải học thuộc — nó là hiện thân của việc phân rã bạn vừa làm: một nhóm bước xứng đáng có tên riêng, nhận vào vài giá trị và trả ra đúng một kết quả. Tiêu chí chọn ranh giới rất cụ thể: nhóm bước đó gọi tên được bằng một động từ hay không. "Xác định xếp loại của một điểm" gọi được, nên tách; "làm nốt phần còn lại" thì không. Bên trong hàm, tham số là ô VÀO, thân hàm là ô XỬ LÝ, giá trị return là ô RA — đúng ba ô bạn dùng để đọc đề ở bài 1, thu nhỏ lại vừa một hàm.

Bạn vừa phân rã xong bài đếm học sinh Gioi, Kha, Yeu của cả lớp: đọc điểm từng bạn, so điểm với 8, so điểm với 5, cộng vào đúng biến đếm, lặp qua cả lớp, in kết quả. Nhìn danh sách bước đó, một câu hỏi tự nhiên xuất hiện: bước nào nên gom lại thành một khối riêng có tên, bước nào cứ để nguyên trong luồng chính?

Nhiều người mới học coi hàm là một cú pháp cần ghi nhớ: gõ def, thụt lề, xong. Nhưng học vậy thì bạn không biết KHI NÀO nên dùng nó — tách hàm bừa bãi, hoặc viết một mạch từ đầu tới cuối mà không tách gì cả.

Bài này giải thích hàm bằng đúng thứ bạn đã có trong tay: ba ô vào-xử lý-ra từ bài 1, và một tiêu chí duy nhất để quyết định ranh giới.

1. Analogy — máy pha cà phê tự động: bỏ gì vào, lấy gì ra

Cứ hình dung một máy pha cà phê tự động ở văn phòng. Bạn không cần biết bên trong máy làm gì; chỉ cần biết hai đầu của nó: bấm nút chọn loại cà phê và cỡ cốc là đầu vào, cốc cà phê nhận ở khay ra là đầu ra. Hàm trong code y hệt: người GỌI hàm không cần đọc từng dòng bên trong, chỉ cần biết đưa gì vào và nhận lại gì.

Đời thườngHàm trong code
Bấm nút chọn loại cà phê, cỡ cốcTham số truyền vào lúc gọi hàm
Máy pha bên trong — bạn không cần biết chi tiếtThân hàm, phần xử lý
Cốc cà phê nhận được ở khay raGiá trị hàm return về
💡 Cách nhớ

Nhìn một lời gọi hàm, hỏi đúng ba câu: cái gì đi vào, máy làm gì bên trong, cái gì đi ra. Ba câu đó là đúng ba ô bạn đã dùng ở bài 1.

2. Hàm là I-P-O thu nhỏ — khung ba ô bài 1 nằm gọn trong một hàm

Ở bài 1, bạn bóc một đề bài thành ba ô: input (đề cho gì), xử lý (làm gì với nó), output (trả ra gì). Khung đó lặp lại y nguyên bên trong một hàm, chỉ thu nhỏ phạm vi lại. Với hàm ở mục 3: tham số là ô VÀO, phần so sánh bên trong là ô XỬ LÝ, giá trị return là ô RA.

Ba ô vào-xử lý-ra bạn dùng để bóc một đề bài lớn, giờ thu nhỏ lại vừa đúng một hàm: tham số là ô vào, thân hàm là ô xử lý, giá trị trả về là ô ra.

3. Ranh giới hàm — tiêu chí "gọi tên được bằng một động từ"

Tiêu chí chỉ có một câu, và nó dứt khoát: một nhóm bước xứng đáng có tên khi bạn gọi được nó bằng MỘT động từ. "Xếp loại một điểm" gọi được, nên tách. "Làm nốt phần còn lại" thì không, nên để nguyên.

Nhìn lại lời giải đã phân rã ở bài trước: bạn có một nhóm bước lặp qua từng bạn — đọc điểm, xác định xếp loại, cộng vào đúng biến đếm. Trong nhóm đó, đúng một phần gọi tên được bằng một động từ rõ ràng: xếp loại một điểm. Phần bao quanh — đọc điểm, cộng biến đếm, lặp qua cả lớp — không gọi tên gọn được; nó chỉ là "làm nốt phần còn lại". Vì vậy chỉ nhóm đầu tách thành hàm.

def xep_loai(diem):
    if diem >= 8:
        return "Gioi"
    elif diem >= 5:
        return "Kha"
    else:
        return "Yeu"
  • Tham số diem — ô VÀO của hàm.
  • Giá trị trả về — một chuỗi xếp loại ("Gioi", "Kha", hoặc "Yeu") — ô RA.

Sơ đồ: khung có nhãn def xep_loai(diem) bao quanh một hộp chứa ba dòng nếu diem từ 8 trở lên ra Gioi, còn nếu từ 5 trở lên ra Kha, còn lại ra Yeu; bên trái khung là hộp nét đứt ghi diem chú thích tham số là ô VÀO của hàm, mũi tên đi vào khung; bên phải là hộp xanh lá ghi một chuỗi xếp loại chú thích giá trị trả về là ô RA, mũi tên đi ra từ khung

Phần còn lại — đọc điểm, gọi xep_loai, cộng biến đếm, lặp cho hết lớp — cứ để nguyên trong luồng chính. Chỉ nhóm nào đặt được cái tên một-động-từ mới đáng tách.

4. Tham số là một phép gán ngầm — vì sao đổi trong hàm không đụng ra ngoài

Khi bạn gọi xep_loai(7), một điều xảy ra mà cú pháp không nói thẳng ra: máy tạo một ô nhớ MỚI tên diem, rồi gán giá trị 7 vào đó — y hệt dòng lệnh diem = 7 bạn đã học ở bài 2 module 1.

Gọi hàm tạo ra một phép gán: tham số diem là một ô nhớ MỚI, được gán bằng giá trị bạn truyền vào lúc gọi. Đây là lý do đổi diem bên trong hàm không đụng tới biến bên ngoài — hai ô độc lập, đúng nguyên tắc "một ô chỉ giữ một giá trị" bạn đã học.

Ô diem chỉ sống bên trong hàm, và biến mất khi hàm chạy xong — kể cả khi biến bên ngoài tình cờ mang tên giống hệt tham số. Mục 5 cho bạn tự đoán chuyện gì xảy ra khi thân hàm sửa lại giá trị của tham số.

5. Thử đoán — đổi tham số bên trong hàm có ảnh hưởng gì ra ngoài?

diem_hs = 7

def xep_loai(diem):
    diem = diem + 100
    return diem

ket_qua = xep_loai(diem_hs)
print(diem_hs)
print(ket_qua)
🖊️ Thử đoán

Trước khi đọc tiếp: dòng print(diem_hs) in ra số nào, và dòng print(ket_qua) in ra số nào? Viết ra hai con số bạn tin chắc, kèm lý do, trước khi xem đáp án.

Kết quả

6. Tới lượt bạn — chọn ranh giới cho một lời giải khác

Một đề khác đã phân rã sẵn: tính tiền vé xe buýt cho một nhóm khách. Trẻ em dưới 6 tuổi miễn phí, có thẻ học sinh-sinh viên giảm nửa giá, còn lại trả đủ. Các bước đã phân rã: đọc tuổi và tình trạng thẻ của từng khách, xác định mức giá cho một khách, cộng dồn tổng tiền, lặp cho hết danh sách, in tổng tiền.

Tự làm trước khi xem gợi ý

Áp tiêu chí ở mục 3: nhóm bước nào xứng đáng tách thành hàm? Đặt tên bằng một động từ, xác định tham số nào là ô VÀO, giá trị nào là ô RA — trước khi mở gợi ý.

Đối chiếu — một cách chọn ranh giới

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

Nhầm 1 — hàm không đẩy giá trị ra ngoài (dùng print thay vì return):

def xep_loai_sai(diem):
    if diem >= 8:
        print("Gioi")
    elif diem >= 5:
        print("Kha")
    else:
        print("Yeu")

ket_qua = xep_loai_sai(7)
print(ket_qua)

Dòng cuối in ra None: xep_loai_sai chỉ IN lên màn hình chứ không return gì cả, nên ket_qua không nhận được giá trị nào để dùng lại. Biến thể tinh vi hơn của cùng lỗi này là hàm tính xong rồi gán vào một biến cục bộ — ket_qua = x * 2 rồi hết hàm — cũng trả về None, vì tính ra giá trị và đẩy giá trị đó ra ngoài là hai việc khác nhau.

✅ Kết thúc hàm bằng return như xep_loai ở mục 3.

Nhầm 2 — tách hàm trước khi phân rã xong, khi các bước còn mơ hồ, dễ đặt sai ranh giới rồi phải sửa lại từ đầu.

✅ Phân rã xong rồi mới quyết định nhóm nào đáng tách hàm.

Nhầm 3 — đặt tên hàm bằng một động từ mơ hồ, ví dụ xu_ly() hay lam_viec() — không nói được hàm làm CHÍNH XÁC việc gì.

✅ Đặt tên cụ thể như xep_loai, tinh_gia_ve — đọc tên là đoán được việc.

8. 📚 Đào sâu — vì sao "gọi tên được bằng một động từ" là tiêu chí tốt

📚 Đào sâu (tuỳ chọn)

Nguồn:

Tên là một hợp đồng: đặt được một tên rõ bằng một động từ, nghĩa là bạn đã biết chắc nhóm đó làm đúng MỘT việc — người gọi hàm chỉ đọc tên là biết hàm làm gì. Ngược lại, bí tên hoặc phải ghép nhiều động từ bằng "và" (doc_va_xu_ly_va_in) là dấu hiệu nhóm bước đang làm nhiều việc cùng lúc — khó kiểm chứng, khó tái dùng.

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

  • Bài 1 — Đọc đề — khung ba ô input, xử lý, output được thu nhỏ lại vừa một hàm ở bài này.
  • Bài 4 — Phân rã — hàm chỉ đóng gói một nhóm bước đã phân rã sẵn; phải phân rã xong rồi mới chọn ranh giới hàm.
  • Bài 2 module 1 — Biến và phép gán — cơ chế "một ô chỉ giữ một giá trị" giải thích vì sao tham số hàm độc lập với biến bên ngoài.
  • Bài 6 — Ca biên — bước tiếp theo: soi các ca biên (rỗng, một phần tử, số 0, số âm, trùng) cho đúng lời giải đã phân rã và tách hàm ở bài này.

10. Tóm tắt

  • Áp tiêu chí lên hai ví dụ đã chạy trong bài: xep_loai(diem)tinh_gia_ve(tuoi, co_the) đều tách được vì gọi tên bằng một động từ; còn cụm "đọc danh sách, cộng dồn, lặp cho hết" thì không — nó là "làm nốt phần còn lại", và đó là dấu hiệu đừng tách.
  • Ô tham số do lời gọi tạo ra là ô nhớ mới: diem_hs bên ngoài vẫn giữ 7 sau khi hàm sửa diem thành 107, và 107 chỉ ra được ngoài vì có return chép nó ra.
  • Quên return thì hàm âm thầm trả về None — chương trình không báo lỗi gì, nhưng giá trị dùng lại sẽ sai.
  • Chỉ tách hàm sau khi đã phân rã xong; tách sớm khi các bước còn mơ hồ dễ đặt sai ranh giới.

11. Tự kiểm tra

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    Vì sao nói hàm là một 'I-P-O thu nhỏ'? Nêu đúng vai trò của tham số, thân hàm, và giá trị trả về.
  2. Q2
    Áp dụng tiêu chí 'gọi tên được bằng một động từ' — vì sao nhóm bước 'xác định xếp loại của một điểm' tách thành hàm được, còn nhóm 'đọc điểm, cộng biến đếm, lặp qua cả lớp' thì không?
  3. Q3
    Đoạn sau in ra hai dòng nào? Giải thích cơ chế, không chỉ nêu kết quả.
    so_du = 100
    
    def rut_tien(so_du, so_tien):
      so_du = so_du - so_tien
      return so_du
    
    con_lai = rut_tien(so_du, 30)
    print(so_du)
    print(con_lai)
  4. Q4
    Vì sao một hàm không có return lại trả về None mà không báo lỗi gì? Điều này nguy hiểm ở chỗ nào?
  5. Q5
    Tách hàm quá sớm, trước khi phân rã xong, có vấn đề gì?
  6. Q6
    Đặt tên hàm là xu_ly_diem thay vì xep_loai có vấn đề gì theo tiêu chí đã học ở bài này?

Bài tiếp theo: Ca biên — rỗng, một phần tử, số 0, số âm, trùng

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

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