Trong lĩnh vực khoa học máy tính, hàm phụ (subprogram) hay subroutine là một phần của mã nguồn được tổ chức thành một khối độc lập, thực hiện những tác vụ nhất định mà chương trình cần thực hiện lặp đi lặp lại từ nhiều vị trí trong quá trình chạy. Khi cần thực hiện tác vụ này, chương trình sẽ gọi đến hàm phụ và nhận kết quả sau khi hàm hoàn tất.
Từ khi máy tính ra đời, phương pháp lập trình theo cấu trúc mô-đun với các hàm phụ đã được áp dụng và phát triển trong các ngôn ngữ lập trình hợp ngữ. Hiện nay, trong các ngôn ngữ bậc cao, hàm phụ được biểu đạt dưới các tên gọi như hàm (function), thủ tục (procedure) và phương thức (method). Một số ngôn ngữ, như Pascal và FORTRAN, phân biệt giữa hàm (chương trình con trả về giá trị) và thủ tục (không trả về giá trị). Trong khi đó, các ngôn ngữ khác như C và LISP coi hai khái niệm này là tương đương. Tên gọi phương thức thường được sử dụng trong lập trình hướng đối tượng để chỉ các hàm phụ là phần của các đối tượng.
Trong một chương trình, một hàm phụ có thể gọi đến hàm phụ khác hoặc gọi chính nó. Tuy nhiên, nếu có sự gọi lẫn nhau, ví dụ subroutine A gọi subroutine B và subroutine B lại gọi subroutine A, sẽ dẫn đến lỗi không xác định khi thực hiện. Một số ngôn ngữ lập trình hỗ trợ phát hiện lỗi này trong quá trình soạn thảo và biên dịch. Để tránh lỗi, khi lập trình cần tuân thủ mô hình gọi theo 'cành và lá', trong đó 'cành' là hàm phụ gọi hàm phụ khác, còn 'lá' là hàm phụ không có lệnh gọi.
Các khái niệm cơ bản
Kỹ thuật lập trình đã dẫn đến việc tổ chức chương trình theo cấu trúc mô-đun, tức là chia chương trình thành nhiều mô-đun hay đơn vị được gọi là subroutine, và trong chương trình chính sẽ thực hiện gọi chúng. Điều này mang lại nhiều lợi ích cho lập trình viên:
- Thay thế các đoạn mã lặp lại bằng một subroutine, giúp mã nguồn ngắn gọn hơn, rõ ràng hơn và dễ bảo trì hơn.
- Đưa các subroutine đã được kiểm tra vào thư viện (library) dưới dạng văn bản trình hoặc mã, để khi lập trình, chỉ cần liên kết đến thư viện đó.
- Những chương trình lớn được thiết kế theo cấu trúc tốt có thể phân công cho nhiều nhóm hoặc lập trình viên khác nhau, và đôi khi có thể thuê người khác viết các subroutine đơn giản.
Vì tầm quan trọng của việc tổ chức chương trình theo cấu trúc, ngay từ những ngày đầu của ngành công nghiệp máy tính, các nhà sản xuất đã chú trọng đến lệnh call và cấu trúc của các chương trình con, ngay cả khi lập trình ở mức mã máy. Khi giải mã ngược (unassemble) mã chương trình, có thể thấy lệnh call xuất hiện rất thường xuyên.
Ngoài các subroutine thực sự, một số ngôn ngữ lập trình, bao gồm cả hợp ngữ, hỗ trợ các dạng chương trình con (trong văn bản trình) mà khi biên dịch sẽ được thay thế bằng đoạn mã, mà không tạo ra subroutine dạng mã thực sự.
- Macro: Dịch nội dung của macro với tên chỉ định và chèn vào vị trí tương ứng.
- Inline: Chuyển nội dung mã được viết dưới dạng hex trong procedure với tên chỉ định và chèn vào vị trí tương ứng.
Những hạn chế
Một trong những nhược điểm chính của việc sử dụng chương trình con là việc cần phải chèn các mã chỉ thị 'dọn dẹp' (housekeeping code) vào trong chương trình con, điều này có thể làm tăng thời gian thực thi tác vụ so với việc mã được viết trực tiếp trong chương trình chính.
Khi điều khiển chuyển sang chương trình con, cần phải lưu trữ các giá trị của con trỏ bộ xử lý tại các điểm vào (entry). Khi chương trình con kết thúc (exit), các giá trị con trỏ được phục hồi từ bản sao lưu trước khi thoát. Những đoạn mã để lưu và phục hồi này thường giống nhau ở nhiều chương trình con, nghĩa là nếu subroutine 'không thực hiện công việc gì' thì khi biên dịch, mã vẫn sẽ bao gồm các đoạn mã này.
Do đó, khi lập trình cho các vi điều khiển với tốc độ thấp và bộ nhớ hạn chế, cần cân nhắc giữa việc sử dụng hay không sử dụng chương trình con. Các CPU hiện đại có tốc độ rất cao nên thời gian mất mát này không đáng kể, vì vậy lập trình viên ứng dụng không cần quá lo lắng.