Go Zero to Hero - Bài 23: Khai báo Hàm (Function) và Hàm ẩn danh (Anonymous Function)
Trong các ngôn ngữ như C++ hay Java, kiểu dữ liệu trả về thường được đặt trước tên hàm (int calculate()). Tuy nhiên, Go áp dụng tư duy "đọc từ trái sang phải" tự nhiên hơn: Từ khóa -> Tên hàm -> Tham số đầu vào -> Kiểu trả về.
1. Cú pháp khai báo cơ bản: Gọn gàng và Rõ ràng
package main
import "fmt"
// Cú pháp: func Tên_hàm(tham_số kiểu_dữ_liệu) Kiểu_trả_về { ... }
func calculateDiscount(price float64, discountRate float64) float64 {
return price - (price * discountRate)
}
// Cú pháp rút gọn tham số:
// Nếu các tham số đứng cạnh nhau CÙNG KIỂU, bạn chỉ cần ghi kiểu ở tham số cuối.
func multiply(a, b, c int) int {
return a * b * c
}
func main() {
finalPrice := calculateDiscount(100000, 0.2)
fmt.Println("Giá sau giảm:", finalPrice)
}
2. Đặc sản của Go: Trả về nhiều giá trị (Multiple Return Values)
Đây chính là tính năng "ăn tiền" nhất của Go so với các ngôn ngữ tiền bối. Thay vì phải tạo ra một Object/Struct tạm thời chỉ để gói ghém nhiều giá trị trả về, Go cho phép bạn return bao nhiêu giá trị tùy thích.
Tính năng này là xương sống của triết lý xử lý lỗi (Error Handling) trong Go: Một hàm thường trả về Kết quả và một Lỗi (Error) đi kèm.
package main
import (
"errors"
"fmt"
)
// Trả về 2 giá trị: (int, error)
func divide(a, b int) (int, error) {
if b == 0 {
// Trả về kết quả mặc định (0) và thông báo lỗi
return 0, errors.New("không thể chia cho 0")
}
return a / b, nil // nil nghĩa là không có lỗi
}
func main() {
// Hứng 2 giá trị cùng lúc
result, err := divide(10, 2)
if err != nil {
fmt.Println("Lỗi xảy ra:", err)
} else {
fmt.Println("Kết quả phép chia:", result)
}
}
3. Named Return Values (Giá trị trả về có tên) và Naked Return
Đôi khi, để code tự làm tài liệu (self-documenting), bạn có thể đặt tên luôn cho các biến sẽ được trả về ngay trên phần khai báo hàm.
Lúc này, Go sẽ tự động khởi tạo các biến đó với Zero Value, và bạn chỉ cần gọi từ khóa return trống không (Naked Return).
// Khai báo sẵn biến result và err ở phần chữ ký hàm
func processTicket(ticketID string) (result string, err error) {
if ticketID == "" {
err = errors.New("mã vé không hợp lệ")
return // Naked return: Tự động trả về giá trị hiện tại của result và err
}
result = "Vé " + ticketID + " đã được xử lý"
return // Tự động trả về result và err (lúc này err đang là nil)
}
⚠️ Lời khuyên thực chiến: Naked Return chỉ nên dùng cho những hàm cực kỳ ngắn (khoảng dưới 10 dòng code). Đối với những hàm logic dài, việc dùng Naked Return sẽ khiến đồng nghiệp của bạn bị "lạc trôi", không biết chính xác biến nào đang được trả về ở cuối hàm.
4. Hàm ẩn danh (Anonymous Function) và Kỹ thuật IIFE
Hàm ẩn danh là những hàm không có tên. Chúng được khai báo ngay tại nơi cần sử dụng và thường được lưu vào một biến, hoặc thực thi ngay lập tức.
Khai báo và gán vào biến
Trong Go, Function là một "First-class citizen" (Công dân hạng nhất), nghĩa là nó có thể được truyền qua lại như một biến bình thường (int, string...).
func main() {
// Khai báo một hàm ẩn danh và gán vào biến logSystem
logSystem := func(message string) {
fmt.Println("[SYSTEM LOG]:", message)
}
// Gọi hàm thông qua biến
logSystem("Khởi động server thành công.")
logSystem("Kết nối RabbitMQ thành công.")
}
Kỹ thuật IIFE (Immediately Invoked Function Expression)
Có những đoạn logic (như tính toán cấu hình ban đầu, hoặc dọn dẹp bộ nhớ) bạn chỉ muốn nó chạy đúng 1 lần duy nhất ngay lúc khai báo, không cần lưu lại để gọi lần sau.
Bạn bọc ngoặc đơn () ở cuối hàm ẩn danh để yêu cầu Go thực thi nó ngay lập tức.
func main() {
// Khai báo và thực thi ngay lập tức (Để ý cặp ngoặc () ở dòng cuối)
func(serviceName string) {
fmt.Printf("Đang khởi tạo cấu hình cho %s...\n", serviceName)
// ... các logic phức tạp
}("Microservice Thanh Toán")
}
Tại sao dân Backend Go lại cuồng Hàm ẩn danh?
Nếu bạn làm việc với các hệ thống phân tán, xử lý hàng ngàn request đồng thời, bạn sẽ sử dụng hàm ẩn danh hàng ngày cho hai mục đích tối thượng:
- Goroutines (Xử lý đa luồng): Để khởi chạy một luồng mới chạy ngầm (chạy song song), bạn chỉ cần đặt từ khóa
gotrước một hàm ẩn danh.go func(id int) { // Đoạn code này sẽ chạy ngầm, không block luồng chính fmt.Println("Đang gửi email biên lai cho user", id) }(1024) - Defer (Trì hoãn thực thi - Dọn dẹp tài nguyên): Khi kết nối DB hay ghi file, bạn phải đảm bảo tài nguyên được đóng lại trước khi thoát hàm, bất chấp hàm có bị lỗi hay không. Hàm ẩn danh kết hợp
deferlà cứu cánh.func queryData() { db := connectDB() // Đảm bảo hàm ẩn danh này LUÔN CHẠY cuối cùng trước khi queryData() kết thúc defer func() { fmt.Println("Đóng kết nối DB.") db.Close() }() // ... logic truy vấn }
Tổng kết
Bạn đã nắm trong tay cách vận hành logic của Go:
- Cú pháp chuẩn: Tên hàm, tham số gộp kiểu, và trả về nhiều giá trị cùng lúc (rất quan trọng cho Error Handling).
- Naked Return: Tiện lợi cho hàm ngắn, nhưng nên cẩn thận với hàm dài.
- Hàm ẩn danh: Vũ khí hạng nặng khi cần nhúng logic xử lý 1 lần, cực kỳ hữu dụng khi kết hợp với
go(đa luồng) vàdefer(dọn dẹp tài nguyên).
All rights reserved