Pure Function trong JavaScript. Tại sao chúng ta nên biết càng sớm càng tốt?

Pure Function trong JavaScript. Tại sao chúng ta nên biết càng sớm càng tốt?

Tin ngắn hàng ngày dành cho bạn
  • Từ lâu rồi suy nghĩ làm thế nào để tăng sự hiện diện thương hiệu, cũng như người dùng cho blog. Nghĩ đi nghĩ lại thì chỉ có cách chia sẻ lên mạng xã hội hoặc trông chờ họ tìm kiếm, cho đến khi...

    In cái áo này được cái tắc đường khỏi phải lăn tăn, càng đông càng vui vì hàng trăm con mắt nhìn thấy cơ mà 🤓

    (Có tác dụng thật nha 🤭)

    » Xem thêm
  • Một vòng của sự phát triển nhiều dự án khá là thú vị. Tóm tắt lại trong 3 bước: Thấy một cái gì đó phức tạp -> Làm cho nó đơn giản đi -> Thêm thắt tính năng cho đến khi nó phức tạp... -> Quay trở lại vòng lặp mới.

    Tại sao lại như vậy? Để mình lấy 2 ví dụ cho các bạn thấy.

    Markdown ra đời với mục tiêu tạo ra một định dạng văn bản thô "dễ viết, dễ đọc, dễ dàng chuyển thành một dạng gì đó như HTML". Vì thời đó chẳng ai đủ kiên nhẫn mà vừa ngồi viết vừa thêm định dạng cho văn bản hiển thị ở trên web như thế nào. Ấy vậy mà giờ đây người ta đang "nhồi nhét" hoặc tạo ra các biến thể dựa trên markdown để bổ sung thêm nhiều định dạng mới đến mức... chẳng nhớ nổi hết cú pháp.

    React cũng là một ví dụ. Từ thời PHP, việc khát khao tạo ra một cái gì đó tách biệt hẳn giao diện người dùng và phần xử lý logic chính của ứng dụng thành 2 phần riêng biệt cho dễ đọc, dễ viết. Kết quả là các thư viện UI/UX phát triển rất mạnh mẽ, mang lại khả năng tương tác với người dùng rất tốt, còn phần logic ứng dụng thì nằm ở một máy chủ riêng biệt. Bộ đôi Front-end, Back-end cũng từ đấy mà thịnh hành, không thể thiếu anh bồi bàn REST API. Ấy vậy mà giờ đây React trông cũng không khác biệt gì so với PHP là mấy, kéo theo là cả Vue, Svelte... lại cùng quy tất cả về một mối.

    Cơ mà không phải vòng lặp là xấu, ngược lại vòng lặp này mang tính tiến hoá nhiều hơn là "cải lùi". Nhiều khi lại tạo ra được cái hay hơi cái cũ thế là người ta lại dựa trên cái hay đó để tiếp tục lặp. Nói cách khác là chắc lọc tinh hoa từng tí một tí một á 😁

    » Xem thêm
  • Song song với các dự án chính thức thì thi thoảng mình vẫn thấy các dự án "bên lề" nhằm tối ưu hoặc cải tiến ngôn ngữ theo khía cạnh nào đó. Ví dụ nature-lang/nature là một dự án hướng tới cải tiến Go, mang lại một số thay đổi nhằm giúp cho việc sử dụng Go trở nên thân thiện hơn.

    Nhìn lại mới thấy hao hao JavaScript 😆

    » Xem thêm

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.

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.

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (1)

Nội dung bình luận...
Avatar
Trịnh Cường4 năm trước

Bài viết rất hay và bổ ích. cảm ơn bạn rất nhiều vì đã chia sẻ. hóng những bài viết mới của bạn. Chúc blog ngày càng lớn mạnh. tặng bạn 1 share

Trả lời
Avatar
Xuân Hoài Tống4 năm trước

Nhớ ghé blog của mình để đọc những bài mới hơn nhé bạn.