Hướng dẫn code “sạch” trong Javascript

Trước khi đến với các phương pháp code “sạch” trong Javascript, chúng ta cần phải hiểu code “sạch” là gì trước đã.

Code sạch là gì?

Code sạch không có nghĩa là phải format với prettier, đú theo các best practices, hay để lại vài dòng comment tà tưa vào code để gợi ý. Nó còn phụ thuộc vào sự đồng cảm của người sau sẽ đọc nó, cố gắng hiểu và sử dụng code của bạn. Điều này có nghĩa là chúng ta phải chọn các phương pháp hoặc các cách viết thay thế để đạt được cùng một mục tiêu dựa trên sự phù hợp với bối cảnh của code.

Trước khi muốn code “sạch” các bạn phải tự hỏi chính mình mấy câu này trước:
  • Bạn có hiểu rõ chuyện gì đang xảy ra không 😀 (Bạn cần đạt được điều gì?)
  • Đọc lại dòng code lần nữa, nó có làm bạn nản không (Có thể phải cần thêm ly cafe cho có động lực ha)
  • Bạn có làm nó phức tạp hơn thực tế đơn giản của nó (Viết xoắn não tí để thể hiện chút với mấy em Fresher chẳng hạn?)

Code sạch giúp giảm đau đầu, truyền đạt ý định hiệu quả hơn và giảm thời gian cần thiết để mở rộng logic sau này, cải thiện hoặc thêm vào chức năng.

Thay đổi thói quen của bạn

Đôi lúc, code của bạn vô cùng hoàn hảo, chạy được, và có thể dễ đọc? Tuy nhiên bạn lại tốn quá nhiều thời gian để giải quyết vấn đề. Có thể đó là bởi vì bạn không chịu đổi mới, sử dụng những syntax hữu hiệu hơn để giải quyết, luôn mắc kẹt trong cách giải quyết và suy nghĩ của mình.

Có thể đã đến lúc bạn cần thay đổi thói quen của mình rồi. Thay thế bằng những syntax hay cách viết ngắn hơn, rõ ràng hơn và có thể giải quyết cùng một vấn đề. Luôn luôn đặt câu hỏi cho bản thân để thực hành và quen với cách tiếp cận mới.

Những syntax hay cú pháp ngắn cũng có những giới hạn của nó. Không phải lúc nào cũng luôn là sự lựa chọn đúng đắn. Hãy sử dụng chúng một cách khéo léo và khôn ngoan.

Nếu bạn có cài đặt một linter được cấu hình tốt, bạn có thể thêm vào đó những quy tắc để giúp bạn viết những syntax ưa thích

Hãy bắt đầu với những dòng đơn giản đầu tiên để có thể code sạch trong Javascript

Đơn giản hóa việc kiểm tra các giá trị “truthy”

Tận dụng mọi khả năng của ‘truthy’

Thay vì sử dụng một điều kiên if để kiểm tra một giá trị có truthy hay không, bạn có thể đơn giản hóa bằng cách sử dụng chính biến đó như là một điều kiện. Tuy nhiên bạn cần hiểu rõ logic đằng sau giá trị truthy.

  • Một giá trị được gọi là truthy, nếu không phải là false00n""nullundefined, và NaN .
Trước
if (condition !== undefined && condition !== null) {
    // Xử lý điều kiện truthy
}
Sau
if (condition) {
    // Xử lý điều kiện truthy
}

Toán tử ba ngôi

Hãy dừng việc sử dụng quá nhiều if-else

Toán tử ba ngôi thường được sử dụng như là cách viết tắt của if. Nó đơn giản là hỏi một câu hỏi (đặt điều kiện), và với nó bạn có thể quyết định thực thi một mã nguồn phụ thuộc vào câu trả lời là yes hay no.

Có 3 phần chính của mệnh đề này: điều kiện cần xác thực, biểu thức nếu đúng và biểu thức nếu sai.

condition ? exprIfTrue : exprIfFalse
Trước
if (num > 5) {
   
   return 'number is above 5';
  
} else {
  
   return 'number is not above 5';
  
}
Sau
return num > 5 ? 'number is above 5' : 'number is not above 5';

Khi toán tử ba ngôi trở nên hỗn loạn

Toán tử ba ngôi được khuyến cáo chỉ sử dụng cho một biểu thức độc lập. Bạn có thể sử dụng để thực thi dựa trên các điều kiện như sau:

condition ? executeThis() : executeOther() hoặc dùng để gán const num = condition ? 3 : 5; .

Tuy nhiên nó sẽ trở nên khá hỗn độn và gây khó chịu hết sức khi bạn lạm dụng quá mức như trong trường hợp dưới đây:

((typeof val === 'string' ? runStringFunction() : false) ? runFunctionWhenStringFuncTrue() ? 'ok' : 'might be ok') : 'exited early so not ok'

Bạn code và nhìn vào thì có thể hiểu nhưng những người khác thì đoán xem?

Một khi logic đã bắt đầu trở nên phức tạp và khó hiểu, bạn nên chia nhỏ chúng ra thành những logic if-else nhỏ hơn

if (typeof val === 'string') {
  if (runStringFunction()) {
       
     if (runFunctionWhenStringFuncTrue()) {
        return 'ok';
     }
     
     return 'might be ok';
  }
}
return 'exited early so not ok';
Bonus (loại bỏ ‘else’)

Như các bạn có thể thấy trong ví dụ trên, chúng ta có thể loại bỏ else nếu return trong mệnh đề if. Câu lệnh return sẽ giúp cho hàm trả về sớm hơn, loại bỏ else dư thừa.

Rút gọn điều kiện với || và && (điều kiện ngắn mạch)

(Có thể kết hợp cả điều kiện if-else và gán biến luôn không?)

Đây là một dạng điều kiện trong đó toán tử logic được sử dụng, với thứ tự ưu tiên để kiểm tra câu lệnh có nên lấy giá trị trả về sớm hay không (ngắn mạch). Nó cho phép bạn ngăn chặn việc thực thi những mã không cần thiết.

  • (Điều kiện sai) && expr trả về giá trị falsy trước theo thứ tự biểu thức
  • (Điều kiện đúng) || expr trả về giá trị truthy trước theo thứ tự biểu thức
Trước
let value; // chúng ta muốn gán 'other' cho 'value' nếu nó không null hay undefined

if (other !== undefined && other !== null) {
  
    value = other;
  
    return value;
}
Sau
const other = '';

value = other || 'other was falsy'; // sẽ trả về 'other was falsy'
Thêm một số ví dụ
const other = '';

const value = other || 'other falsy'; // Trả về 'other falsy'

other = 'Im truthy now';

value = other && ''; // Trả về 'Im falsy now' 

Tuy nhiên hãy cẩn thận khi logic bắt đầu trở nên phức tạp. Bạn sẽ phải dè chừng việc sử dụng các toán tử này khi có quá nhiều toán tử logic chồng chéo. Nó sẽ biến thành cái tổ sinh sản bug (đơn giản nhưng dễ bỏ lỡ).

Giá trị ưu tiên là tối quan trọng và cần chú ý trong toán tử loại này, hãy nhớ bao các logic nhỏ trong ngoặc đơn.

true || false && false // trả về true, bởi vì '&&' được thực thi trước
(true || false) && false // trả về false, bởi có dấu ngoặc đơn bọc logic trước

Hàm mũi tên (Arrow functions)

Arrow function là một cú pháp thay thế cho hàm bình thường. Nó không có bindings đến this , arguments , super hay new.target.

Sử dụng một Arrow function làm cho mã nguồn của chúng ta ngắn gọn, đơn giản và phạm vi hóa hơn.

Trước
const func = function() {
    // do something
}
Sau
const func = () => { return 'something'; }
func();
// hoặc
const func = () => 'something';
// hoặc với tham số
func();
const func = arg => {
   console.log(arg);
}
func('hello');
// với nhiều tham số
const func = (arg, arg1) => {
    console.log('arg: ', arg);
    console.log('arg1: ', arg1);
};

func('hello', 'there');

Vòng lặp

Sử dụng forEach cho vòng lặp mảng

Nó cho phép bạn lặp qua từng phần tử một. Tham chiếu trên mỗi phần tử được cung cấp thông qua tham số callback. Cú pháp này trông sạch sẽ hơn và dễ dàng để hiểu logic hơn.

Trước
const array = ['1', '2', '3'];
for (const val of array) {
  
}
// hoặc
for (let i = 0; i < array.length; i++) {
  
}
Sau
const array = ['1', '2', '3'];
array.forEach(val => {
    // do something
})

Tuy nhiên bạn không thể thoát khỏi vòng lặp forEach sớm được, thay vào đó sử dụng for..of . Nếu bạn cần thoát vòng lặp sớm hơn thì hãy sử dụng những lựa chọn khác để thay thế.

Có một chút vấn đề nhỏ về hiệu suất của forEach nếu so sánh với các vòng lặp khác. Tuy nhiên, với những tác vụ đơn giản thì nó không khác biệt

Toán tử mở rộng (Spread Operator)

Spread Operator cho phép một mảng hoặc object được mở rộng.

Trước
const array1 = [0, 1, 2, 3, 4];
const array2 = [5, 6, 7, 8, 9];

function(array1, array2) {
  
  for (const value of array2) {
    
    array1.push(value);
    
  }
}
Sau
const array = [0, 1, 2, 3, 4];
const other = [5, 6, 7, 8, 9];

function(array1, array2) {

    return [ ...array1, ...array2 ];
    
}

Giá trị mặc định trong Object

Khi chúng ta gán giá trị vào object, thỉnh thoảng chúng ta cần kiểm tra giá trị đó có truthy hay không (được định nghĩa và không null…)

Nếu nó là falsy bạn có thể muốn sử dụng giá trị mặc định. Bạn có thể sử dụng spread operator trong trường hợp này. Spread thứ hai có thể ghi đè lên những giá trị truthy lên spread thứ nhất, nếu không thì sẽ giữ giá trị mặc định.

Các bạn xem ví dụ bên dưới để hiểu rõ hơn:

Trước
function handleParams(params?: any) {
const finalParams = {
    
    one: params && params.one ? params.one,
    two: params && params.two? params.two,
    three: params && params.three ? params.three
};
   
}
Sau
const defaultValues = {
   one: 'oneDefault',
   two: 'twoDefault'
   three: 'threeDefault'
};

function handleParams(params?: any) {
    const finalParams = { ...defaultValues, ...params };
    return finalParams;
}

handleParams({one: 'oneNew', three: 'threeNew'});
// sẽ trả về {one: 'oneNew', two: 'twoDefault', three: 'threeNew'}

Đếm ngược While

Đây là một cách viết ngắn gọn khác cho vòng lặp for. Sử dụng while với giá trị giảm dần val– sẽ đến ngược đến 0 và kết thúc vòng lặp.

Với mình thì nó trông gọn gàng hơn và dễ hiểu hơn cho người đọc.

Trước
for (let i; i < array.length; i++) {
    // abc, xyz...
}
Sau
const length = array.length;
while(length--) {
    // abc, xyz...
}

Toán tử bit NOT

Kiểm tra một phần tử tồn tại trong mảng

Toán tử bit not thường được sử dụng với indexOf để kiểm tra xem một giá trị có tồn tại trong một mảng hay chuỗi không.

Chúng ta thường kiểm tra giá trị trả về từ indexOf có lớn hơn -1 không, để đơn giản hóa việc này toán tử bit not ~  sẽ trả về true nếu indexOf trả về một index lớn hơn 0, nghĩa là giá trị này có tồn tại trong mảng hoặc chuỗi.

Logic đằng sau toán tử bit NOT là ~n == -(n+1) .

Trước
const array = [0, 1, 2, 3, 4];
if (array.indexOf(5) > -1) {
  
   return 'exists';
  
} else {
  
    return 'does not exist';
  
}
Sau
const array = [0, 1, 2, 3, 4];

if (~array.indexOf(4)) // true

Chúng ta còn có thể sử dụng những hàm tương tự khác như Array.prototype.find và Array.prototype.includes .

if (array.includes(3)) // true
  
if (array.find(val => val === 2)) // true

Code “sạch” trong Javascript với Self-Calling Anonymous Functions (IIFE)

Chúng ta sử dụng IIFE (Immediately-Invoked Function Expression) khi chúng ta có một đoạn code muốn được thực thi ngay lập tức bởi trình duyệt mà không cần lời gọi hàm và tên hàm.

Nó kết hợp cả khai báo và thực thi chỉ trong một biểu thức.

Trước
function random() {
    // do something
}

random()
Sau
!function() {
}()
// hoặc
(function() {})()

Bạn có thể sử dụng bất kỳ toán tử đơn nào thay cho !

Bao gồm +, -, ~

~function(){}()
+function(){}()

Điểm quan trọng cần lưu ý

Những biểu thức viết gọn này nhằm mục đích làm giúp cho bạn có thể code “sạch” trong javascript, mã nguồn sẽ trông đơn giản và gọn gàng hơn. Dễ bảo trì và phát triển.

Tuy nhiên, để thật sự xây dựng một mã nguồn javascript dễ đọc nhất, chặt chẽ đi kèm với hơi hướng hướng đối tượng và chuyên nghiệp hơn, các bạn có thể thử qua Typescript. Typescript là một ngôn ngữ thật sự thú vị, các bạn muốn tìm hiểu qua có thể tham khảo bài viết này: https://huynhphuchuy.com/nodejs/typescript-la-gi-co-an-duoc-khong/

Chúc các bạn một ngày làm việc hiệu quả và code càng ngày càng “sạch” hơn!

Write a comment