Một số cách để viết mã dễ đọc hơn trong JavaScript/Node.js

Bài viết được sự cho phép của tác giả Tống Xuân Hoài

Vấn đề

Điều mà tôi tin rằng rất nhiều lập trình viên mong muốn tiến tới là việc viết mã sao cho dễ đọc dễ hiểu. Bằng chứng là có rất nhiều Design Pattern được đưa ra để hướng dẫn mọi người giải quyết vấn đề theo cách mà nhiều người vẫn làm. Nhưng đó chưa phải là tất cả, việc viết mã hoàn chỉnh thì lại phụ thuộc vào từng lập trình viên.

Chúng ta có nhiều công cụ hỗ trợ soạn thảo mã nguồn theo nhiều cách. Như định dạng, màu sắc, giao diện hiển thị, công cụ hỗ trợ gỡ lỗi… thoải mái lựa chọn theo sở thích hoặc cùng làm việc nhóm. Bên cạnh đó, vẫn có những quy tắc được đặt ra để các thành viên tham gia phát triển phải tuân theo. Tạm bỏ qua vấn đề định dạng (format), ngày hôm nay chúng ta hãy cùng nhau tìm hiểu xem có cách nào để viết mã dễ đọc hơn không nhé!

Một số cách làm mã dễ đọc hơn

Thông thường, những người viết mã khó bảo trì thường mang trong mình phong cách Ninja mà tôi có một bài viết chi tiết tại Nhắc đến Ninja Code – họ là ai mà khiến cho nhiều người phải “khiếp sợ”?. Để viết mã dễ đọc, ngoài cách “rửa tay gác kiếm” khỏi giới nhẫn giả, thì dưới đây là một số cách bạn đọc có thể tham khảo từ chính tôi.

Tên biến và tên hàm

Biến và hàm là hai thành phần cơ bản và quan trọng nhất của bất kì chương trình nào. Việc đặt cho chúng những cái tên dễ hiểu là điều hết sức quan trọng trong việc đọc hiểu mã.

Có nhiều nguyên tắc đặt tên biến và hàm mà bạn có thể tìm thấy trên mạng. Ví dụ tôi thường đặt tên biến theo kiểu camelCase, là danh từ, gắn liền với nội dung mà nó nắm giữ, các biến thuộc dạng hằng số hoặc chỉ thay đổi theo biến môi trường thì sẽ viết hoa và theo định dạng UPPER_CASE… Tên hàm gắn liền với một hành động, nghĩa là động từ, theo kiểu camelCase. Chức năng của hàm gắn liền với cái tên của nó, chỉ làm một công việc còn nếu nhiều hơn, tách chúng thành các hàm nhỏ hơn.

  Lập trình bất đồng bộ là gì? Tại sao JavaScript là ngôn ngữ lập trình bất đồng bộ?

  Bạn có suy nghĩ như thế nào khi tôi nói rằng Node.js rất nhanh?

Tránh Magic Number hay Hardcoded Value

Thi thoảng trong chương trình, chúng ta hay bắt gặp việc so sánh biến với một con số nào đó ví dụ như:

if (user.lastSeen < 4321)
...

4321 lúc này được gọi là Magic Number hay Hardcoded Value, chúng ta không biết chính xác con số đó biểu thị điều gì đơn giản vì nó không có tên. Điều này khiến người đọc mã phải bối rối về sự xuất hiện của chúng. Thay vào đó, hãy sử dụng một biến để biểu thị cho ý nghĩ của những con số này.

const timeActive = 4321;
if (timeExpired < timeActive)
...

Xem thêm việc làm Node.js developer hấp dẫn nhất tại TopDev

Lệnh điều kiện

Có một sự thật là đoạn mã nào thụt lề càng nhiều thì logic sẽ càng nhiều và càng phức tạp. Hãy xem xét một ví dụ dưới đây.

async function main() {
  const user = await getUser(id);

  if (user) {
    if (user.lastSeen) {
      const userSeen = dayjs(user.lastSeen);
    } else {
      const userSeen = dayjs();
      if (userSeen.isAfter(dayjs().subtract(1, "day"))) {
        showUserOnline();
      } else {
        showUserOffline();
      }
    }
  }
}

Thật ra không có một quy tắc cụ thể nào cho việc sử dụng if...else, nhưng hãy nhìn vào ví dụ trên có phải if...else càng nhiều logic sẽ càng phức tạp, sau này có bảo trì, sửa lại mã cũng trở nên khó khăn hơn vì có nhiều logic cần được làm sáng tỏ trước khi bắt tay vào sửa mã.

Nếu có thể, hãy hạn chế các lệnh điều kiện if...else lồng nhau. Sử dụng switch...case thay thế trong những trường hợp phức tạp hơn, và tốt nhất hãy làm “phẳng” cây logic nhất có thể.

Xem việc làm javascript đãi ngộ tốt trên TopDev

Hạn chế Side-Effect

Có thể nói Side Effect đã làm thay đổi hoàn toàn cách viết mã của tôi từ khi biết đến. Để nói về vấn đề này, tôi có một bài viết chi tiết là Pure Function trong Javascript. Tại sao chúng ta nên biết càng sớm càng tốt?.

Nhìn chung, việc hạn chế Side-Effect giúp chúng ta có một cấu trúc chương trình rành mạch hơn, tránh việc gọi một hàm thôi mà làm thay đổi hết trạng thái của chương trình.

Áp dụng functional

Bất cứ chỗ nào xử lý logic, hãy tách thành những hàm nhỏ hơn và đặt tên cho chúng. Hãy xem xét một ví dụ dưới đây:

const userRoleMap = {
  0: 'admin',
  1: 'moderator',
  2: 'user',
};

fetch('https://api.example.com/users')
  .then(response => response.json())
  .then((user) => {
    return user.filter(user => user.active)
  })
  .then((user) => {
    return user.map(user => {
      return {
        id: user.id,
        name: user.name,
        roleName: userRoleMap[user.role],
      }
    })
  });

Đoạn mã trên lấy danh sách users từ một endpoint về, lọc ra danh sách users đang kích hoạt và sau đó thêm thuộc tính roleName dựa vào role của mỗi users.

Có thể mất một thời gian để hiểu được ý nghĩa của đoạn mã trên, vì chúng ta đang phải tập trung vào logic để hiểu. Thay vào đó, đoạn mã trên có thể được viết lại bằng cách tách các đoạn mã xử lý logic ra thành các hàm nhỏ hơn.

const userRoleMap = {
  0: 'admin',
  1: 'moderator',
  2: 'user',
};

const filterActiveUsers = (users) => {
  return users.filter(user => user.active)
};

const mapUsersRole = (users) => {
  return users.map(user => {
    return {
      id: user.id,
      name: user.name,
      role: userRoleMap[user.role],
    }
  })
};

fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(filterActiveUsers)
  .then(mapUsersRole);

Hãy nhìn vào phần thân chương trình, toàn bộ logic đã hoàn toàn lộ rõ là filterActiveUsers và mapUsersRole. Điều này giúp chúng ta nhanh chóng nắm bắt được luồng xử lý dữ liệu, sau này nếu muốn xem chi tiết chỉ cần nhảy đến các hàm liên quan.

Sử dụng comment hợp lý

Comment là một vấn đề gây nhiều tranh cãi, có người nói ai càng comment chứng tỏ càng thiếu kinh nghiệm vì họ không biết cách viết mã sao cho dễ đọc!? Quan điểm của tôi là hãy sử dụng comment đúng lúc và không quá phụ thuộc vào nó để giải thích chi tiết cách mã của bạn hoạt động.

Kết hợp nhiều yếu tố như đặt tên và các hàm functional để gián tiếp giải thích cách mã của bạn hoạt động, trừ khi gặp phải logic quá phức tạp hoặc cần mô tả ngắn gọn luồng dữ liệu thì hãy để lại comment. Các thay đổi quan trọng có liên kết đến tài liệu bên ngoài…

Ví dụ:

# 02/09/2023: Thay đổi logic theo tài liệu DOC-2945
function getRandomUser() {
...

Ngoài ra, còn nhiều trường hợp khác để bạn khéo léo vận dụng cách comment ví dụ như sử dụng một tính năng nào đó của thư viện ngoài.

# Limits Environment variables
# https://developers.cloudflare.com/workers/platform/limits/#environment-variables
function setEnv() {
...

Như trong ví dụ trên, tôi đang sử dụng một tính năng biến môi trường của Cloudflare và cần tuân thủ một số quy tắc liên quan đến giới hạn, việc đính kèm tài liệu sẽ giúp cho đồng nghiệp dễ dàng truy cứu lại tài liệu và hiểu được cách tôi viết mã hơn.

Tài liệu hóa mã mà bạn viết

Dĩ nhiên một chương trình phức tạp không thể nào chỉ dựa vào mỗi việc đọc mã thôi mà có thể hiểu hết được. Bên cạnh đó, chúng ta cần có một hệ thống tài liệu mô tả hầu hết luồng hoạt động của chương trình. Nó vừa giúp cho người mới có tài liệu để nghiên cứu và giúp cho bản thân phát huy khả năng hệ thống hoá.

Hãy rèn luyện cho mình kỹ năng viết và truyền đạt lại những thứ mình làm. Đây cũng là một trong những kỹ năng cần thiết để giúp bạn tiến xa hơn trên con đường sự nghiệp.

Cuối cùng, vẫn là không ngừng học hỏi

Học, học nữa, học mãi… Học không bao giờ là đủ và cũng không bao giờ thừa. Điều tôi nói trên đây chỉ là quan điểm cá nhân, ngoài ra tôi biết còn nhiều điều cần phải học hỏi thêm từ người khác. Bạn có thể học hỏi thêm từ đồng nghiệp hoặc thông qua các dự án mã nguồn mở.

Tổng kết

Trên đây là một số các quy tắc mà tôi vẫn đang áp dụng trong công việc hàng ngày, tuy không đánh giá được mức độ tốt đến đâu nhưng ít nhất sau vài ba tuần đọc lại mã của mình viết ra, tôi vẫn có thể hiểu được chúng :D. Còn bạn, bạn có đang áp dụng thêm phương pháp nào để giúp cho mã dễ đọc hơn không? Hãy để lại bình luận nhé!

Bài viết gốc được đăng tải tại 2coffee.dev

Xem thêm:

Xem thêm việc làm công nghệ hấp dẫn trên TopDev