+4

Clean Code: 7 tips to write clean functions

Motivation

Nếu mất hơn 3 giây để hiểu một hàm làm gì, đã đến lúc bạn nên tái cấu trúc nó. Chất lượng hàm của bạn tỉ lệ nghịch với thời gian cần để hiểu chúng.

Các hàm phức tạp có thể dẫn đến lỗi, làm cho việc thay đổi trở nên khó khăn và làm chậm quá trình làm quen cho các developer khác. Hãy nhớ rằng, mã nguồn được đọc nhiều hơn là viết, vì vậy đầu tư thời gian vào việc viết các clean functions là một trong những khoản đầu tư tốt nhất mà bạn có thể thực hiện trong thời gian dài.

Dưới đây là 7 mẹo về cách tôi viết clean functions.

1. Keep your functions small

Uncle Bob đã từng nói:

The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.

Nguyên tắc đầu tiên của các hàm là chúng nên nhỏ gọn. Nguyên tắc thứ hai là chúng nên nhỏ hơn nữa.

Một hàm nên làm một việc và làm tốt việc đó. Nhưng kích thước lý tưởng của một hàm là bao nhiêu? Không có quy tắc cứng nhắc cho điều này. Đôi khi 5 dòng là vừa đủ, có khi cần 50 dòng để đảm bảo một trách nhiệm duy nhất.

Điều tốt nhất là luôn sử dụng sự phán đoán của bạn dựa trên ngữ cảnh. Hãy thực dụng, đừng cứng nhắc. Mẹo là cố gắng viết các hàm nhỏ nhưng tránh tạo ra quá nhiều hàm đến mức gây rối cho code của bạn.

2. Name your functions well

Không có tuần nào mà tôi không gặp vấn đề các function được đặt tên kém chất lượng. Trái ngược với suy nghĩ phổ biến rằng đặt tên cho function không khó. Nó đòi hỏi rất nhiều sự nỗ lực, vài lần thử nghiệm, và sự tinh chỉnh liên tục.

Dưới đây là 4 mẹo tôi sử dụng để đặt tên cho các hàm của mình:

  • Sử dụng tên tiết lộ ý định liên quan đến lĩnh vực kinh doanh. Hãy nhớ rằng, nếu code của bạn không nói bằng ngôn ngữ của khách hàng, bạn không tập trung vào vấn đề của họ.
  • Sử dụng động từ và cụm động từ. Việc sử dụng danh từ hoặc tính từ cho tên function có thể gây vấn đề vì chúng không thể hiện rõ ràng chức năng.
  • Sử dụng quy ước đặt tên trong nhóm của bạn.
  • Đừng sử dụng các thuật ngữ khác nhau cho cùng một khái niệm. Điều đó làm cho code của bạn không nhất quán, gây nhầm lẫn cho bạn và đồng nghiệp. Thay vào đó, chỉ sử dụng một từ cho mỗi khái niệm.

naming-function.png

3. Limit the number of parameters

Số lượng đối số lý tưởng cho một hàm là 0. Vấn đề với các hàm có quá nhiều tham số là làm tăng độ phức tạp và khiến hàm khó kiểm thử hơn.

limit-param.jpg

Hãy cố gắng giới hạn tối đa 3 tham số cho mỗi hàm. Một giải pháp tuyệt vời cho vấn đề này là nhóm các tham số liên quan lại với nhau. group-param.jpg

4. Reduce nesting with a return early

Tránh sử dụng các câu lệnh IF lồng nhau trong một hàm. Chúng làm tăng độ phức tạp và giảm tính duy trì. if-else-function.jpg

Thay vào đó, hãy đảo ngược các điều kiện và sử dụng các điều kiện bảo vệ (guard clauses). Điều này sẽ giúp mã của bạn dễ theo dõi hơn. Thêm vào đó, bạn sẽ loại bỏ được các câu lệnh ELSE. if-else-in-function.jpg

5. Write pure function with no side effects

Pure function là gì? Một hàm được gọi là pure function nếu nó luôn output giống nhau khi input giống nhau. Thứ hai, nó không có tác động phụ (side effects). Nói cách khác, đầu ra chỉ phụ thuộc vào đầu vào mà không có các hành vi ẩn.

Có 3 lợi ích của các pure function:

  • Mã nguồn trở nên dễ dự đoán hơn.
  • Chúng dễ kiểm thử hơn.
  • Chúng ta có thể chạy chúng song song.

Bạn biết rằng bạn đang làm việc với clean code khi mọi hàm bạn đọc đều làm chính xác những gì bạn mong đợi. Các pure function làm cho code của bạn trở nên clean hơn.

6. Avoid boolean flag parameters

Việc sử dụng các biến kiểu boolean làm tham số thường dẫn đến mã nguồn khó hiểu. Có hai vấn đề chính với điều này.

  • Thứ nhất, khi gọi một hàm với cờ true hoặc false, không rõ giá trị đó có ý nghĩa gì.
  • Thứ hai, rất khó để mở rộng hành vi liên quan đến các cờ đó.

Bây giờ, hãy cho tôi biết, sự khác biệt giữa hai cuộc gọi hàm sau là gì? boolean-param.jpg

Và giờ hãy cho tôi biết, sự khác biệt giữa hai cuộc gọi hàm này là gì? boolean-param-function.jpg

Thay vì sử dụng boolean, hãy sử dụng Enums để làm cho code của bạn dễ hiểu hơn. Thay đổi nhỏ, tác động lớn.

7. Use comments sparingly

Khi một hàm trở nên khó hiểu, đừng cố gắng cải thiện nó bằng cách thêm comments. Comments là một trong những dấu hiệu mã nguồn kém

  • Comments dễ bị lỗi thời.
  • Comments thường thừa thãi.
  • Thường thì các dev khác lười đọc comments.

comment-function.jpg

Comments là công cụ tốt để giải thích WHY, nhưng chúng nên là phương án cuối cùng để giải thích WHAT. Trong hầu hết các trường hợp, bạn có thể thay thế comments bằng cách sử dụng các tên hàm phù hợp. Đừng quên: Một tên hàm mô tả dài là tốt hơn một comment mô tả dài. comment-in-function.jpg

Summary

Chúng ta dành gấp 10 lần thời gian để đọc mã nguồn so với việc viết mã. Việc viết các clean functions là một trong những cách mạnh mẽ nhất để tạo ra mã nguồn dễ đọc.

Hãy nhớ rằng: Lập trình không chỉ là nói cho máy tính biết phải làm gì. Đó là nói cho một lập trình viên khác biết bạn muốn máy tính làm gì.

Mỗi dòng mã bạn viết là một thông điệp gửi đến các đồng nghiệp của bạn. Hãy làm cho nó rõ ràng, ngắn gọn và thuyết phục.


Tham khảo: https://craftbettersoftware.com/p/clean-code-7-tips-to-write-clean


8. [Bonus] Nâng cấp kỹ thuật tạo tham số trong Hàm

Tham khảo: https://viblo.asia/p/javascript-nang-cap-ky-thuat-tao-tham-so-trong-ham-js-GAWVpxwaV05

Ví dụ chúng ta được giao một task nhỏ: Tạo một hàm in ra thông tin người dùng. Với yêu cầu này, chúng ta sẽ cùng nhau áp dụng lần lượt 3 kỹ thuật tạo tham số trong hàm dưới đây.

8-1. Kỹ thuật Truyền Thống

// Khởi tạo giá trị mặc định cho age và address
function printPerson(name, age = 24, address = 'Hà Nội') {
  console.log(name, age, address)
}

printPerson('pdthien') 
// pdthien, 24, Hà Nội

Vấn đề bắt đầu phát sinh khi hàm printPerson đã được gọi ở nhiều nơi trong project. Khi ta cần thêm tham số mới vào hàm, đa phần để xử lý ổn thỏa tránh ảnh hưởng tới project, ta thường thêm tham số mới vào cuối hàm.

Nghiệp vụ yêu cầu in thêm thông tin ngày sinh của người dùng ta xử lý như sau:

// Bổ sung thêm tham số birthday
function printPerson(name, age = 24, address = 'Hà Nội', birthday) {
  console.log(name, age, address, birthday)
}

// Phải truyền giá trị cho age và address
printPerson('pdthien', 24, 'Hà Nội', '01/01/1999')
// pdthien, 24, Hà Nội, 01/01/1999
  • Ưu điểm:
    • Phù hợp với hàm có ít tham số
    • Khi gọi hàm thì biết được số lượng các tham số phải truyền vào
    • Biết được tham số nào là required, tham số nào là optional
  • Nhược điểm:
    • Khi gọi hàm, ta phải truyền đúng thứ tự và đầy đủ giá trị của tham số, mặc dù như ví dụ trên, giá trị của trường age và address không hề thay đổi.
    • Khó quản lý hơn khi hàm của ta chứa quá nhiều tham số.

8-2. Kỹ thuật "Bỏ trứng vào cùng 1 giỏ"

Tạo ra 1 biến object bên ngoài để chứa các tham số cần truyền vào hàm, khi gọi hàm thì chỉ cần truyền biến này vào

const info = { name: 'pdthien'}

function printPerson(info) {
  // Khởi tạo giá trị mặc định cho trường age và address
  const { name, age = 24, address = 'Hà Nội' } = info
  console.log( name, age, address)
}

printPerson(info)
// pdthien, 24, Hà Nội

Nếu cần bổ sung thêm trường mới, ta chỉ việc gắn thêm trường đó vào tham số object của hàm.

Nghiệp vụ yêu cầu in thêm thông tin ngày sinh của người dùng ta xử lý như sau:

const info = { name: 'pdthien', birthday: '01/01/1999' }

function printPerson(info) {
  // Khởi tạo giá trị mặc định cho trường age và address
  const { name, age = 24, address = 'Hà Nội', birthday } = info
  console.log( name, age, address, birthday)
}

printPerson(info)
// pdthien, 24, Hà Nội, 01/01/1999
  • Ưu điểm:
    • Không cần quan tâm thứ tự của các field cần truyền
    • Không cần điền giá trị mặc định của field nếu không có sự thay đổi (age và address)
  • Nhược điểm:
    • Không biết được các field nào cần truyền, có thể truyền thừa hoặc thiếu field
    • Muốn biết rõ hàm cần field nào thì phải đi vào khai báo hàm để đọc code gây mất thời gian

8-3. Kỹ thuật Destructuring

// Khởi tạo giá trị mặc định cho age và address
function printPerson({ name, age = 24, address = 'Hà Nội' }) {
  console.log(name, age, address)
}

printPerson({ name: 'pdthien' })
// pdthien, 24, Hà Nội

Nghiệp vụ yêu cầu in thêm thông tin ngày sinh của người dùng ta xử lý như sau:

// Khởi tạo giá trị mặc định cho age và address
function printPerson({ name, age = 24, address = 'Hà Nội', birthday }) {
  console.log(name, age, address, birthday)
}

printPerson({ name: 'pdthien', birthday: '01/01/1999' })
// pdthien, 24, Hà Nội, 01/01/1999
  • Ưu điểm
    • Truyền tham số mà không cần quan tâm đến thứ tự các trường, giúp việc code trở nên linh hoạt.
    • Không cần gán lại giá trị mặc định của một trường nếu không có sự thay đổi (age và address)
    • Trong các công cụ lập trình (VSCode), khi gọi hàm sẽ được gợi ý các trường cần truyền, giúp việc đọc tham số trong hàm tường minh hơn.

8-4. Kỹ thuật rest và spread

Ta áp dụng kỹ thuật này khi hàm không biết rõ số lượng tham số truyền vào, có khi chỉ truyền 1 tham số, có khi truyền cả chục tham số

function sum (...params) {  // đây là 'rest', params lúc này là 1 mảng
  let total = 0
  params.forEach(param => total = total + param)
  return total
}

const array = [1, 1, 2, 3, 5, 8]
console.log(sum(...array))    // đây là 'spread'


// Dòng trên có thể được viết theo kiểu này
// Có thể truyền tiếp n số đằng sau nữa vẫn được
console.log(sum(1, 1, 2, 3, 5, 8))

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí