Viết clean code không phải là một việc dễ dàng, và cần thời gian để luyện tập, clean code ban đầu tuy mất thời gian hơn nhưng sẽ giúp chúng ta tiết kiệm thời gian để đọc code sau này, giúp việc cải tiến và mở rộng dự án trở nên nhanh chóng, dễ dàng hơn. Trong bài viết này chúng ta sẽ thảo luận về một số quy ước cần tuân theo để viết clean code.

Lợi ích của việc tuân theo các quy ước:

  • Code “sạch” hơn (clean code)
  • Code chất lượng hơn
  • Code dễ đọc hơn
  • Bảo trì code dễ dàng hơn

“Clean code rất đơn giản và trực tiếp. Clean code cũng giống như một bài văn xuôi được viết mạch lạc. Clean code không làm cho ý tưởng của lập trình viên trở nên khó hiểu mà thay vào đó, nó là những dòng lệnh cụ thể, làm cho những nội dung trừu tượng trở nên rõ ràng, dễ hiểu hơn.” — Robert C. Martin

1. Magic numbers

Magic number có nghĩa là chúng ta đang gán một con số mà không có ý nghĩa rõ ràng. Đôi khi chúng ta sử dụng một giá trị cho một mục đích cụ thể, và chúng ta không gán giá trị đó cho một biến có ý nghĩa. Vấn đề xảy ra là sau đó có người làm việc với code của bạn, họ không biết được ý nghĩa của giá trị trực tiếp đó.

// Bad practice
for (let i = 0; i < 60; i++) {
  // do something
}

// Good practice
const MINUTES_OF_THE_HOUR = 60;
for (let i = 0; i < MINUTES_OF_THE_HOUR; i++) {
  // do something
}

2. Tránh dùng nhiều vòng lặp lồng nhau

Đôi khi chúng ta sử dụng nhiều vòng lặp lồng nhau làm code trở nên rất khó hiểu. Giải pháp là tách tất cả các vòng lặp ra thành các function riêng biệt.

Giả sử chúng ta có một mảng (array) chứa một mảng (firstArr), mảng firstArr này lại chứa một mảng khác (secondArr), và chúng ta muốn lấy ra phần tử của mảng trong cùng (secondArr). Để xử lý yêu cầu này, thông thường chúng ta hay viết các vòng lặp lồng nhau, nhưng đây không phải là cách hay. Chúng ra có thể viết một function để giải quyết vấn đề, và function này gọn gàng hơn, ít lặp code hơn, dễ đọc hơn và có thể tái sử dụng.

// Bad practice
const array = [[["hello world"]]];

array.forEach((firstArr) => {
  firstArr.forEach((secondArr) => {
    secondArr.forEach((element) => {
      console.log(element);
    });
  });
});

// Good practice
const array = [[["hello world"]]];

const getValuesOfNestedArray = (element) => {
  if (Array.isArray(element)) {
    return getValuesOfNestedArray(element[0]);
  }

  return element;
};

getValuesOfNestedArray(array);

3. Không phụ thuộc vào comments

Comments giúp các lập trình viên khác làm việc trong cùng một dự án sau này hiểu code nhanh hơn, dễ dàng hơn. Tuy nhiên, nếu code cần đến comments thì có thể là do bản thân code chưa đủ rõ ràng, dễ hiểu. Dưới đây là một câu nói nổi tiếng về việc viết comments của Jeff Atwood:

“Comments vốn dĩ không phải là xấu, nhưng cũng không hẳn là tốt, đôi khi người viết code bị phụ thuộc vào comments. Bản thân bạn phải luôn tự ý thức viết code của mình như thể không có comments. Chính vì vậy, hãy viết code theo cách đơn giản nhất, dễ hiểu nhất, rõ ràng nhất có thể”. — Jeff Atwood

Nhìn chung, code có comments là tốt, nhưng chính bản thân code của bạn cần phải dễ hiểu, dễ giải thích kể cả khi không có comment.

4. Tránh viết function quá dài

Khi một function hoặc một class (lớp) quá dài, thì nên tách thành nhiều phần. Điều này sẽ làm cho code của chúng ta clean hơn, dễ hiểu hơn và có thể tái sử dụng.

Giả sử chúng ta cần cộng và trừ hai số. Thông thường chúng ta có thể dùng một function duy nhất, nhưng cách tốt nhất là nên chia thành hai function. Khi viết các function riêng lẻ, chúng ta có thể tái sử dụng chúng trong toàn bộ ứng dụng.

// Bad practice
const addSub = (a, b) => {
  // add
  const addition = a + b;

  // sub
  const sub = a - b;

  // returning as a string
  return `${addition}${sub}`;
};

// Good practice
// add
const add = (a, b) => {
  return a + b;
};

// sub
const sub = (a, b) => {
  return a - b;
};

5. Tránh lặp code

Lặp code có nghĩa là một khối code được sử dụng trong code của bạn nhiều hơn một lần. Điều này có nghĩa là phần code đó của bạn nên được viết thành một function.

Lấy từ ví dụ ở trên (Mục 2: Tránh dùng nhiều vòng lặp lồng nhau). Nhìn vào phần Bad practice: Chúng ta đã lặp lại khối code tương tự nhau ba lần. Cách tối ưu hơn là viết một function có chức năng tương đương, và function đó có thể tái sử dụng.

// Bad practice
const array = [[["hello world"]]];

array.forEach((firstArr) => {
  firstArr.forEach((secondArr) => {
    secondArr.forEach((element) => {
      console.log(element);
    });
  });
});

// Good practice
const array = [[["hello world"]]];

const getValuesOfNestedArray = (element) => {
  if (Array.isArray(element)) {
    return getValuesOfNestedArray(element[0]);
  }

  return element;
};

getValuesOfNestedArray(array);

Xét thêm một ví dụ khác:

// Bad practice
const getUserCredentials = (user) => {
  const name = user.name;
  const surname = user.surname;
  const password = user.password;
  const email = user.email;
};

// Good practice
const getUserCredentials = (user) => {
  const { name, surname, password, email } = user;
};

6. Đặt tên biến theo chuẩn Camel Case

Camel Case là quy tắc đặt tên cho cả biến, function, cũng như các định danh khác. Theo quy tắc Camel Case, tên phải bắt đầu bằng một chữ cái viết thường và tất cả các chữ cái đầu tiên của những từ tiếp theo sẽ được viết hoa.

let camelCase = "hello";
const thisIsCamelCase = () => {
  // do something
};

7. Đặt tên có ý nghĩa

Đặt tên có ý nghĩa là một trong những quy ước quan trọng nhất. Luôn sử dụng tên có ý nghĩa cho các biến, function và các tên khác. Hãy chọn một cái tên thể hiện ý nghĩa mục đích của bạn.

Nếu chúng ta cần một function lấy thông tin ngân hàng của người dùng, thì chúng ta không nên sử dụng tên như getUserInfo hoặc kiểu tên tương tự. Chúng ta nên sử dụng tên getUserBankInfo thì sẽ cụ thể hơn.

8. Ưu tiên mô tả chi tiết hơn ngắn gọn

Nếu có thể, bạn nên đặt tên ngắn gọn. Tuy nhiên trong trường hợp bạn không chắc cách đặt tên ngắn gọn có thể hiện đủ ý nghĩa hay không, và bạn thấy khó khăn trong việc tìm ra cụm hai, ba từ để đặt tên, thì tốt hơn hết là nên ưu tiên mô tả chi tiết.

Giả sử chúng ta cần đặt tên cho một function để tìm một người dùng thông qua số điện thoại hoặc email của họ. Chúng ta phải chọn một cái tên chi tiết, có ý nghĩa và thể hiện được mục đích của function một cách ngắn gọn.

// Bad practice
const findUser

// Good practice
// descriptive version
const findUserByPhoneOrEmail

// shorter version
const getUserFromDatabase

9. Sử dụng các động từ nhất quán cho mỗi khái niệm

Function thường được sử dụng để thực hiện hành động, ví dụ như đọc, xóa, tạo mới, chỉnh sửa thứ gì đó. Vì vậy, tên function nên là một cụm động từ, và hãy chắc chắn rằng bạn sử dụng một động từ nhất quán cho cùng một hành động.

Nếu chúng ta cần một function CRUD, chúng ta có thể đặt tên chứa động từ create, read, update hoặc delete.

Nếu chúng ta cần lấy thông tin người dùng từ cơ sở dữ liệu, thì không nên đặt tên function là userInfo, user,... Chúng ta nên sử dụng tên getUserInfo.

// Bad practice
function userInfo() {
  // do something
}

// Good practice
function getUserInfo() {
  // do something
}

Giả sử bạn muốn viết các function tìm thông tin email, tìm số điện thoại của người dùng, bạn không nên đặt tên function là getUserEmail, retrieveUserPhone (thực hiện cùng một hành động nhưng sử dụng động từ không nhất quán). Hãy đặt là getUserEmail, getUserPhone (sử dụng 1 động từ get nhất quán).

// Bad practice
function getUserEmail() {
  // do something
}
function retrieveUserPhone() {
  // do something
}

// Good practice
function getUserEmail() {
  // do something
}
function getUserPhone() {
  // do something
}

10. Đặt tên biến Boolean cụ thể hơn trong câu lệnh if-then

Giả sử, bạn muốn kiểm tra các đặc điểm của một chiếc ô tô xem nó có phải là sedan hay không, đã được bán ra chưa, có phải màu xanh không, có túi khí không: sedan, sold, green, airbag.

// Bad practice
let car = {};
car.sedan, car.sold, car.green, car.airbag;

// Good practice
let car = {};
car.isSedan, car.isSold, car.isGreen, car.hasAirbag;

Viết như vậy sẽ cụ thể hơn, tự nhiên hơn và làm cho chương trình dễ giải mã hơn.

11. Sử dụng danh từ khi đặt tên class (lớp) và áp dụng quy tắc Pascal Case

Class không thực hiện hành động, có thể hiểu class là khuôn mẫu để tạo ra các đối tượng, vì vậy không nên sử dụng động từ trong tên class.

Ngoài ra, tên class nên áp dụng quy tắc Pascal Case (viết hoa tất cả các chữ cái đầu tiên của mỗi từ). Camel case thường được sử dụng cho các đối tượng.

// Bad practice
class secretProject {
  // do something
}

// Good practice
class SecretProject {
  // do something
}

12. Viết hoa các giá trị không đổi (Screaming Snake Case)

Luôn sử dụng tên viết hoa đầy đủ cho các hằng số.

Theo quy tắc Screaming Snake Case, tất cả các chữ cái trong một từ được viết hoa và các từ phân tách nhau bằng một dấu gạch dưới.

// Good practice
const DAYS_IN_A_YEAR = 365;

13. Tránh đặt tên biến chỉ có một chữ cái

Thực sự không nên đặt tên biến chỉ có một chữ cái. Tuy nhiên, trong một function nhỏ, đối với vòng lặp, có thể chấp nhận tên biến có một chữ cái cho index.

// Bad practice
const q = () => {
  // do something
};

// Good practice
const query = () => {
  // do something
};

// This is also okay
for (let i = 0; i < 10; i++) {
  // do something
}

Tuân theo những quy ước này về lâu dài sẽ rất có lợi cho bạn. Khi bạn viết code theo những quy ước trên, thì cho dù là ai tiếp nhận code sau này, họ sẽ nhanh chóng hiểu được code của bạn, và có thể tiếp tục thêm các chức năng mới một cách dễ dàng. Clean code là cần thiết cho cả cá nhân và nhóm.

Tham khảo:

12 Conventions for Writing Clean Code

JavaScript Best Practices and Coding Conventions