Tất cả chúng ta đều đã thấy (và viết) code “tồi” tại thời điểm nào đó và hy vọng chúng ta đang làm việc để cải thiện những kỹ năng, không chỉ là tìm hiểu những framework mới nhất trên mạng.

 

Tại sao chúng ta cần viết code sạch, không chỉ vì hiệu suất?

Trong khi hiệu suất của sản phẩm hoặc trang web của bạn là điều rất quan trọng, thì code của bạn trông như thế nào cũng quan trọng không kém. Lý do đằng sau điều này đó là máy tính không phải là “thực thể” duy nhất đọc code của bạn.
Điều đầu tiên, và là điều quan trọng nhất, đó là sau cùng thì sẽ có lúc bạn phải đọc lại một phần nào đó, nếu không muốn nói là toàn bộ các đoạn code của mình, và khi điều đó xảy ra những đoạn code có hiệu suất tốt sẽ không giúp bạn hiểu được những gì bạn đã viết hoặc tìm ra cách khắc phục lỗi.
Thứ hai, nếu bạn làm việc trong một nhóm hoặc cộng tác với những developer khác thì bất cứ khi nào bạn viết code, các thành viên trong nhóm của bạn sẽ phải đọc code của bạn và cố gắng diễn giải theo cách hiểu của họ. Để dễ dàng hơn cho người khác, thì việc lưu ý cách đặt tên biến và tên hàm, độ dài của mỗi dòng, cấu trúc code của bạn và một số thứ khác là điều rất quan trọng.
Cuối cùng, viết code sạch trông thực sự đẹp hơn khi nhìn vào.

 

1. Làm thế nào để xác định được code “tồi”?

Cách đơn giản nhất để xác định được code “tồi”, theo ý kiến ​​của tôi, là cố gắng đọc code của bạn như thể đó là một câu hoặc cụm từ.

 

Ví dụ, đoạn code dưới đây:

 

Phiên bản “xấu” của hàm “traverseUpUntil”

 

Ở hàm bên trên, khi truyền một phần tử và một hàm có điều kiện trả về node cha gần nhất thông qua hàm có điều kiện.

 

const traverseUpUntil = (el, f) => {

 

Theo như ý tưởng rằng code nên được đọc như là viết bình thường, dòng đầu tiên có 3 lỗi nghiêm trọng.

  • Các tham số cho hàm không thể đọc được như các từ.
  • Trong khi el có thể hiểu được vì nó thường được sử dụng với nghĩa là element, thì tham số f không giải thích được mục đích của nó.
  • Nếu bạn dùng hàm này, nó sẽ được đọc như sau: “traverse up until el passes f”, có lẽ sẽ tốt hơn nếu đọc là: “traverse up until f passes, from el”. Đúng là như vậy, cách tốt nhất để thực sự làm điều này đó là gọi hàm này là el.traverseUpUntil(f) nhưng đó lại là một vấn đề khác.

 

 

let p = el.parentNode

 

Đây là dòng thứ hai. Một lần nữa ta lại có vấn đề trong việc đặt tên, lần này là với tên biến. Nếu một người nhìn vào đoạn code này, rất có thể họ sẽ hiểu p là parentNode của tham số el. Tuy nhiên, điều gì xảy ra khi ta nhìn thấy p được sử dụng ở một nơi nào khác, ta sẽ không còn bối cảnh để giải thích nó là gì nữa.

 

 

while (p.parentNode && !f(p)) {

 

Ở dòng này, vấn đề chính chúng ta gặp phải là không biết !f(p) là cái gì hoặc nó làm gì, bởi vì “f” có thể là bất cứ thứ gì tại thời điểm này. Những gì người đọc code phải hiểu đó là !f(p) là một biến check để xem liệu node hiện tại có vượt qua điều kiện hay không. Nếu có, kết thúc vòng lặp.

 

 

p = p.parentNode

 

Dòng này đã tự giải thích khá rõ.

 

 

return p

 

Không rõ 100% những gì đang được trả về do tên biến xấu.

 

2. Hãy cải tiến

 

Phiên bản tốt hơn của hàm “traverseUpUntil”

 

Đầu tiên chúng ta sửa tên các tham số và thứ tự của chúng: (el, f) => thành (condition, node) => (bạn cũng có thể viết là condition => node => bổ sung thêm một layer of useability).

Bạn có thể thắc mắc tại sao thay vì sử dụng “element”, tôi lại sử dụng “node”. Tôi làm thế vì những lý do sau:

  • Chúng ta đang viết code về các node, ví dụ .parentNode, vậy tại sao không làm cho chúng giống nhau.
  • Nó ngắn hơn là viết element mà không làm mất đi ý nghĩa của nó. Và lý do tại sao tôi lại nói điều này là vì nó hoạt động với tất cả các dạng node có thuộc tính “parentNode”, không chỉ là các phần tử HTML.

 

Tiếp theo, chúng ta nói đến tên biến:

 

let parent = node

 

Điều rất quan trọng là phải xây dựng đầy đủ ý nghĩa các biến qua tên gọi của nó, “p” bây giờ là “parent”. Bạn cũng có thể nhận thấy chúng tôi không bắt đầu bằng cách lấy node.parentNode, thay vào đó chúng tôi chỉ lấy node.

 

Điều này dẫn chúng ta đến những dòng tiếp theo:

 

do {
  parent = parent.parentNode
} while (parent.parentNode && !condition(parent))

 

Thay vì một vòng lặp while thông thường tôi đã chọn một vòng lặp do … while. Điều này có nghĩa là chúng ta chỉ phải nhận nút cha một lần, vì nó chạy điều kiện sau một hành động, chứ không phải chạy theo cách khác. Việc sử dụng vòng lặp do… while cũng là để quay trở lại với ý tưởng đọc code như viết.

 

Hãy thử đọc nó: “Do parent equals parent’s parent node while there is a parent node and the condition function doesn’t return true.” Mặc dù nghe có vẻ hơi lạ, nhưng nó giúp chúng ta hiểu đoạn code có nghĩa là gì khi chúng ta có thể dễ dàng đọc nó.

 

return parent

 

Trong khi nhiều người chọn sử dụng biến generic ret (hoặc returnValue), nó không phải là một cách làm tốt để đặt tên biến mà bạn trả về “ret”. Nếu bạn đặt tên cho biến trả về của bạn một cách thích hợp, nó sẽ trở nên rõ ràng hơn với những gì đang được trả về. Tuy nhiên, đôi khi các hàm có thể dài và rắc rối, điều đó khiến nó trở nên khó hiểu hơn. Trong trường hợp này, tôi khuyên các bạn nên chia chức năng của bạn thành nhiều chức năng nhỏ hơn, và nếu nó vẫn quá phức tạp thì nên sử dụng nhiều comments.

 

3. Đơn giản hóa code của bạn

Bạn đã làm cho code của mình có thể đọc được, giờ là lúc loại bỏ những đoạn code nào không cần thiết. Tôi chắc chắn một số người đã nhận ra, có thể không phải lúc nào chúng ta cũng cần đến biến parent.

 

const traverseUpUntil = (condition, node) => {
  do {
    node = node.parentNode
  } while (node.parentNode && !condition(node))
  
  return node
}

 

Những gì tôi đã làm là lấy ra dòng đầu tiên và thay thế “parent” với “node”. Điều này bỏ qua bước không cần thiết của việc khởi tạo “parent” và đi thẳng đến vòng lặp.

 

Nhưng còn tên biến thì sao?

Mặc dù “node” không phải là cách mô tả tốt nhất cho biến này, nó chỉ tạm chấp nhận được thôi. Nhưng đừng nên để như thế, hãy đổi tên nó. Đặt là “currentNode” thì sao?

 

const traverseUpUntil = (condition, currentNode) => {
  do {
    currentNode = currentNode.parentNode
  } while (currentNode.parentNode && !condition(currentNode))
  
  return currentNode
}

 

Tốt hơn rồi! Bây giờ khi đọc nó, chúng ta thấy không có vấn đề gì cả, currentNode sẽ luôn đại diện cho node hiện tại của chúng ta thay vì nó chỉ là một node nào đó.

 

Bài viết được dịch từ medium.com