Tuyệt kỹ viết hàm trong lập trình: Sức mạnh hủy diệt của Pure Function (Hàm thuần túy)
Chào anh em!
Khi bước chân vào con đường lập trình, ai trong chúng ta cũng được học cách viết một cái hàm (function) từ những ngày đầu tiên. Cứ đưa đầu vào (input), xử lý một đống logic ở giữa, rồi trả về đầu ra (output) là xong.
Nhưng nếu không cẩn thận, những cái hàm được viết một cách cẩu thả sẽ biến code base của bạn thành một mớ hỗn độn "bùn lầy". Bạn sửa bug ở module A, đùng một cái module B lăn đùng ra chết mà không hiểu lý do.
Để giải quyết triệt để cơn ác mộng này, các lập trình viên lão làng luôn tuân thủ một tiêu chuẩn vàng: Pure Function (Hàm thuần túy). Hãy cùng mổ xẻ xem khái niệm này quyền năng đến mức nào nhé!
1. Pure Function thực chất là gì?
Hiểu một cách đơn giản nhất, một Pure Function là một hàm thỏa mãn đúng hai điều kiện kiên quyết sau:
- Tính tất định (Deterministic): Cùng một đầu vào (arguments) cố định, hàm bắt buộc phải trả về đúng một kết quả duy nhất, không bao giờ thay đổi, bất chấp việc bạn gọi nó bao nhiêu lần hay gọi ở đâu.
- Không có tác dụng phụ (No Side Effects): Hàm chỉ làm nhiệm vụ tính toán trên dữ liệu được truyền vào và trả về kết quả. Nó tuyệt đối không được làm thay đổi bất kỳ trạng thái nào bên ngoài phạm vi của nó (không đổi giá trị của biến toàn cục, không ghi file ngầm, không sửa đổi trực tiếp object/array truyền vào, không gọi API ngầm...).
Ví dụ kinh điển về một Pure Function:
// Đây là Pure Function hoàn hảo
function calculateArea(radius) {
return Math.PI * radius * radius;
}
Dù bạn gọi calculateArea(5) một triệu lần, kết quả vẫn luôn chính xác là 78.539.... Hàm này không hề làm thay đổi bất kỳ biến nào ở bên ngoài nó.
Ví dụ về một Impure Function (Hàm "vẩn đục"):
let taxRate = 0.1;
// Đây là Impure Function tồi tệ
function calculateTotal(price) {
return price + (price * taxRate);
}
Tại sao nó không thuần túy? Vì kết quả của nó phụ thuộc vào biến toàn cục taxRate. Nếu một đoạn code ở đâu đó lỡ tay đổi taxRate = 0.2, lập tức hàm calculateTotal sẽ trả về kết quả khác dù bạn truyền vào cùng một số price. Nó vi phạm tính tất định!
2. Tác dụng phụ (Side Effects) - Kẻ thù ẩn mặt
Trong thực tế lập trình, Side Effects chính là nguyên nhân của 90% các con bug "ma làm" khó debug nhất.
Hãy tưởng tượng bạn truyền một mảng dữ liệu người dùng vào một hàm để sắp xếp (sort):
let users = [{name: 'B'}, {name: 'A'}];
function sortUsers(list) {
return list.sort(); // Hàm sort() mặc định trong nhiều ngôn ngữ làm thay đổi mảng gốc!
}
Khi bạn gọi sortUsers(users), mảng users ban đầu ở bên ngoài bỗng dưng bị biến đổi vị trí. Nếu một đoạn code khác ở phía dưới cũng đang phụ thuộc vào thứ tự gốc của mảng users, nó sẽ bị lỗi văng tung tóe.
Cách viết chuẩn Pure Function cho trường hợp này: Thay vì sửa trực tiếp dữ liệu cũ, hãy tạo ra bản sao (clone) và xử lý trên bản sao đó:
function sortUsers(list) {
// Tạo mảng mới bằng spread operator trước khi sort để không ảnh hưởng mảng gốc
return [...list].sort();
}
3. Tại sao Pure Function lại là "chén thánh" của Clean Code?
Việc ép bản thân viết code theo mô hình Pure Function mang lại những lợi ích khủng khiếp cho dự án:
- Dễ dàng kiểm thử tuyệt đối (Easy to Test): Bạn không cần phải mock Database, không cần giả lập API, không cần dựng Context phức tạp. Việc viết Unit Test cho Pure Function dễ như ăn kẹo: Truyền đầu vào A, kiểm tra xem đầu ra có đúng là B không là xong.
- Khả năng tối ưu hóa cực đỉnh (Memoization / Caching): Vì cùng một đầu vào luôn cho ra một kết quả, bạn có thể lưu trữ (cache) lại kết quả của những lần tính toán nặng nhọc trước đó. Lần sau gọi lại với đúng tham số đó, hệ thống chỉ việc trả về kết quả trong cache thay vì phải tính lại từ đầu, giúp tăng tốc độ ứng dụng đáng kể.
- An toàn trong lập trình bất đồng bộ (Thread-safe / Concurrency): Trong các hệ thống lớn xử lý đa luồng (Multi-threading) hoặc bất đồng bộ (như NodeJS hay Go), các hàm impure cực kỳ nguy hiểm vì chúng tranh chấp dữ liệu (Race Condition). Pure Function hoàn toàn miễn nhiễm với lỗi này vì nó đứng độc lập và không bao giờ tranh chấp tài nguyên chung.
- Tâm lý nhẹ nhõm khi Refactor: Khi bạn nhìn vào một Pure Function, bạn đọc hiểu nó ngay lập tức mà không cần phải đi dò xem biến này ở file khác đang bị ai thay đổi.
Lời kết
Không phải lúc nào chúng ta cũng viết được 100% Pure Function, vì một ứng dụng thực tế kiểu gì cũng phải ghi log, gọi Database, hay kết nối API (những việc bắt buộc phải có Side Effects).
Tuy nhiên, bí quyết của một kỹ sư phần mềm giỏi là cách ly tối đa phần logic nghiệp vụ vào bên trong các Pure Function, và đẩy các Side Effects ra những rìa ngoài cùng của hệ thống. Hãy áp dụng tư duy này vào dự án tiếp theo, bạn sẽ thấy code của mình chạy mượt mà, ít bug và dễ bảo trì hơn rất nhiều!
All rights reserved