Rethinking JavaScript Part II - Death of the For Loop (Translated)
for loop của JavaScript đã đáp ứng rất tốt nhu cầu của chúng ta, tuy nhiên thì hiện tại nó đã lỗi thời và nên được nghỉ ngơi và thay bằng cách kỹ thuật lập trình hàm mới hơn. May thay, đây là một thay đổi không đòi hỏi bạn phải là một người thông thạo về lập trình hàm. Thậm chí còn tốt hơn ...
for loop của JavaScript đã đáp ứng rất tốt nhu cầu của chúng ta, tuy nhiên thì hiện tại nó đã lỗi thời và nên được nghỉ ngơi và thay bằng cách kỹ thuật lập trình hàm mới hơn.
May thay, đây là một thay đổi không đòi hỏi bạn phải là một người thông thạo về lập trình hàm. Thậm chí còn tốt hơn khi đây là một cái gì đó mà bạn có thể thực hiện ở các project hiện tại.
Đâu là vấn đề với for loop?
Thiết kế của for loop "khuyến khích" việc thay đổi trạng thái và sử dụng các hiệu ứng lề (side effect), cả hai đều là những nguyên nhân tiềm tàng gây ra các đoạn code có nhiều lỗi và không thể lường trước được.
Tất cả chúng ta đều biết rằng trạng thái toàn cục (global state) là rất tồi và cần được loại bỏ. Mặc dù trạng thái cục bộ (local state) cũng tệ chẳng kém, chúng ta lại thường không thường xuyên để ý đến nó bởi ảnh hưởng của nó ở phạm vi nhỏ hơn. Vậy nên thực sự thì chúng ta chưa bao giờ giải quyết được vấn đề mà chúng ta chỉ giảm thiểu nó mà thôi.
Với một trạng thái thay đổi được, ở một thời điểm nào đó, một biến (variable) sẽ thay đổi vì một lý do nào đó và bạn sẽ mất hàng giờ đồng hồ để debug và tìm lý do tại sao mà giá trị thay đổi. Tôi đã từng vò đầu bứt tai rất nhiều suy nghĩ về điều này.
Tiếp theo, tôi muốn nói nhanh về hiệu ứng lề. Những từ ngữ này nghe có vẻ ghê gớm - hiệu ứng lề. Bạn có muốn chương trình của bạn có hiệu ứng lề? Không, tôi không muốn!
Nhưng hiệu ứng lề là gì?
Một hàm được coi là có hiệu ứng lề nếu nó chỉnh sửa những thứ nằm ngoài phạm vi của hàm. Nó có thể là thay đổi giá trị của một biến, đọc input từ bàn phím (keyboard input), gọi API, viết data lên đĩa, ghi log ra console, v.v.
Các hiệu ứng lề nên bị loại bỏ khi có thể. Các hàm với hiệu ứng lề thường khó test và khó hiểu, vậy nên hãy loại trừ nó bất cứ khi nào có thể. May thay chúng ta sẽ không còn phải lo lắng nhiều về hiệu ứng lề ở đây.
Okay, bớt miệng và thay vào đó là code. Hãy nhìn vào đoạn code với for loop thông thường sau:
const cats = [ { name: 'Mojo', months: 84 }, { name: 'Mao-Mao', months: 34 }, { name: 'Waffles', months: 4 }, { name: 'Pickles', months: 6 } ] var kittens = [] // typical poorly written `for loop` for (var i = 0; i < cats.length; i++) { if (cats[i].months < 7) { kittens.push(cats[i].name) } } console.log(kittens)
Kế hoạch của tôi là refactor lại đoạn code này từng bước một để bạn có thế thấy việc viết code đẹp thực sự không khó.
Thay đổi đầu tiên tôi muốn làm chính là việc tách câu lệnh if thành một hàm riêng:
const isKitten = cat => cat.months < 7 var kittens = [] for (var i = 0; i < cats.length; i++) { if (isKitten(cats[i])) { kittens.push(cats[i].name) } }
Nhìn chung đây là một thói quen tốt để tách các câu lệnh if. Việc thay đổi filter từ "nhỏ hơn 7 tháng" thành "là một con mèo con" rất quan trọng. Bây giờ khi bạn đọc đoạn code, nội dung trở nên rõ tàng. Tại sao chúng ta lại lấy ra các con mèo dưới bảy tháng tuổi? Điều này thực sự chẳng dễ hiểu tí nào. Mục đích của chúng ta là tìm ra các con mèo con, vậy nên hãy để code nói lên điều đó!
Một lợi ích khác là isKitten có thể tái sử dụng (reusable) và tất cả chúng ta đều biết:
Making our code reusable should always be one of our goals.
Thay đổi tiếp theo chính là tách việc chuyển đổi (transformation) hay ánh xạ (mapping) từ một object kiểu cat thành một cái tên:
const isKitten = cat => cat.months < 7 const getName = cat => cat.name var kittens = [] for (var i = 0; i < cats.length; i++) { if (isKitten(cats[i])) { kittens.push(getName(cats[i])) } }
Tôi đã dự tính viết một vài đoạn văn mô tả cơ chế của filter và map. Nhưng tôi nghĩ bằng cách không mô tả chúng và thay vào đó, việc trình bày cho bạn thấy việc bạn có thể đọc và hiểu đoạn code này dễ ra sao, mặc dù không được giới thiệu về map và filter, sẽ là cách minh họa tốt nhất:
const isKitten = cat => cat.months < 7 const getName = cat => cat.name const kittens = cats.filter(isKitten) .map(getName)
Cũng chú ý rằng là chúng ta đã lược bỏ kittens.push(...). Chúng ta thấy ở đây đã không còn sửa đổi trạng thái, không còn var nữa!
Code that uses const (over var and let) is sexy as hell
Thành thực thì ở đây, lẽ ra chúng ta đã có thể sử dụng const ngay từ đầu nhưng đây là một ví dụ đã được sắp đặt sẵn nên tôi mới vòng vèo như vậy. Hãy bỏ qua cho tôi nhé!
Thao tác refactor cuối cùng tôi muốn đưa ra chính là tách filter và mapping thành hàm riêng:
const isKitten = cat => cat.months < 7 const getName = cat => cat.name const getKittenNames = cats => cats.filter(isKitten) .map(getName) const cats = [ { name: 'Mojo', months: 84 }, { name: 'Mao-Mao', months: 34 }, { name: 'Waffles', months: 4 }, { name: 'Pickles', months: 6 } ] const kittens = getKittenNames(cats) console.log(kittens)
"Cái chết của vòng lặp for" xin phép được kết thúc ở đây. Hẹn gặp lại các bạn trong bài dịch tiếp theo - "Cái chết của break"