Pure Function trong Javascript: Hiểu thế nào cho đúng?
Bài viết được sự cho phép của tác giả Tống Xuân Hoài
Vấn đề
Tôi năm nay đã 26 tuổi, maintain cũng dăm ba dự án rồi mà đôi lúc tôi cũng hay gặp những trường hợp mà một số bạn trong team hay làm thế này:
function convertBirthdayToAges (person) {
const year = new Date().getFullYear(); // 2021
return person.map(p => p.age = year - p.year);
}
...
const persons = [{name: 'Nguyễn Văn A', year: 2000}];
convertBirthdayToAges(persons);
console.log(persons); // [{name: 'Nguyễn Văn A', year: 2000, age: 21}]
Thoạt nhìn cách viết hàm như trên có vẻ bình thường nhưng bạn hãy để ý sau khi persons đi qua hàm convertBirthdayToAges
thì nó đã bị gắn thêm một attribute age
.
Hay một ví dụ khác kiểu như là:
let year = 2020;
function afterManyYear(num) {
return year + num;
}
afterManyYear(5) // 2025;
....
year = 2025;
afterManyYear(5) // 2030;
Ở ví dụ trên, ban đầu khi gọi hàm afterManyYear(5)
kết quả là 2025 nhưng sau đó, do year
bị thay đổi thành 2025 thì afterManyYear(5)
lúc này lại trả về 2030.
Điều này có vẻ cũng bình thường nhưng hãy tưởng tượng trong giai đoạn bảo trì khi bạn không biết year
bị thay đổi ở đâu thì quả là tai hại. Bạn cũng có thể nói thế thì sao không khai báo const
với year
: const year = 2020;
? Thì tôi nghĩ khi đã khai báo với let
thì trong đầu họ đã nghĩ sẽ sẵn sàng thay đổi year
bất kì lúc nào rồi.
Nếu bạn là người thường xuyên làm những điều trên & thấy sự bất tiện của nó thì cũng là lúc các bạn nên biết về khái niệm Pure Function.
Pure function là gì?
Pure function đúng như tên gọi của nó: “Hàm thuần khiết”.
Đó là một hàm JS & thoả mãn hai điều kiện:
- Các đầu vào giống nhau luôn trả về đầu ra giống nhau.
- Không có side-effects.
Các đầu vào giống nhau luôn trả về đầu ra giống nhau
Quá rõ ràng. Ví dụ như:
function add(x, y) {
return x + y;
}
add(1, 2); // 3
add(1, 2); // 3
Với mỗi cặp x
,y
truyền vào thì giá trị trả về không bao giờ thay đổi.
Hàm này sẽ không thoã mãn:
let x = 1;
function add(y) {
return x + y;
}
add(1); // 2
x = 2;
add(1); // 3
Khi một hàm đảm bảo điều kiện này thì chắn chắn việc đọc hiểu & gỡ lỗi sẽ dễ dàng hơn rất nhiều.
Tham khảo việc làm JavaScript tại Hồ Chí Minh trên TopDev
Không có side-effects
Side-effects là những “hiệu ứng” đi kèm trong hàm như:
- Thay đổi giá trị của đầu vào.
- console.log
- HTTP calls (fetch/AJAX).
- Thay đổi một file (fs).
- Query DOM.
- …
Nhìn chung thì ngoài những điều liệt kê ở trên thì side-effects còn bao gồm cả những công việc có trong hàm mà không liên quan đến kết quả tính toán cuối cùng.
Ở ví dụ 1 phần mở đầu ta đã thấy convertBirthdayToAges
đã làm biến đổi giá trị của đầu vào là persons
. Nếu chẳng may persons
bị xoá mất một attribute nào đó thì chẳng phải là một điều rắc rối hay sao!
Để giải quyết vấn đề trên, thay vì chỉnh sửa trực tiếp persons
, chúng ta hãy trả về một đối tượng mới:
function convertBirthdayToAges (person) {
const year = new Date().getFullYear();
return [...person.map(p => p.age = year - p.year)];
}
const persons = [{name: 'Nguyễn Văn A', year: 2000}];
const newPersons = convertBirthdayToAges(persons);
console.log(persons); // [{name: 'Nguyễn Văn A', year: 2000}];
Ví dụ trên tôi đã sử dụng toán tử spread syntax
(…) để tạo ra một đối tượng mảng mới. Lưu ý rằng nó chỉ có thể sao chép “nông” (shallow copy) một đối tượng, để có thể sao chép “sâu” (deep copy) tôi khuyến nghị nên dùng package clone có sẵn trên npm.
Một ứng dụng không thể nào là không có side-effect
Đúng vậy, ứng dụng của bạn không thể nào hoạt động mà không bao gồm các “hiệu ứng” như đã liệt kê ở bên trên trừ chúng quá mức đơn giản. Chúng không thể hoạt động nếu như không đọc – ghi vào database hay select một phần tử trong DOM. Nhưng quan trọng là nên giảm thiểu tối đa hoặc cấu trúc những đoạn mã side-effects một cách độc lập nhất có thể.
Ví dụ như một hàm cập nhật dữ liệu:
// Ví dụ này giả sử Person là một sequelize model
// Hàm update này mới là hàm trực tiếp có side-effect
function update(payload) {
return Person.update(payload);
}
function updatePerson(body) {
const name = body.name.trim();
const year = +body.year;
return update({ name, year });
}
Tổng kết
Pure function không phải là khái niệm mới nhưng những lợi ích mà pure function mang lại trong quá trình phát triển & bảo trì sản phẩm là cực kì tốt dựa trên kinh nghiệm làm việc của tôi.
Qua những ví dụ trên tôi mong rằng các bạn sẽ nhận ra được những lợi ích khi áp dụng pure function vào các dự án trong hiện tại & tương lai. Chỉ cần thay đổi cách diễn đạt một chút sẽ mang lại nhiều lợi ích trong việc bảo trì code sau này.
Bài viết gốc được đăng tải tại 2coffee.dev
Có thể bạn quan tâm:
- 12 Thư viện JavaScript trực quan hoá dữ liệu hot nhất năm 2023
- Kỹ thuật tối ưu Javascript nhằm tăng tốc độ web gấp 2 lần
- Chia sẻ thư viện javascript mở rộng khi kết nối tới Hub sử dụng SignalR
Xem thêm việc làm công nghệ hấp dẫn trên TopDev
- G Giải Quyết Bài Toán Kinh Doanh Bằng Big Data và AI
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- F Framework nào tốt nhất cho dự án của bạn? – Checklist chi tiết
- K Kinh nghiệm xử lý responsive table hiệu quả
- S Stackoverflow là gì? Bí kíp tận dụng Stack Overflow hiệu quả
- 7 7 kinh nghiệm hữu ích khi làm việc với GIT trong dự án
- B Bài tập Python từ cơ bản đến nâng cao (có lời giải)
- B Bảo mật API là gì? Một số nguyên tắc và kỹ thuật cần biết
- H Hướng dẫn cài đặt và tự học lập trình Python cơ bản từ A-Z
- C Chinh Phục Phân Tích Dữ Liệu Với Pandas Trong Python: Hướng Dẫn Từng Bước