Nghệ thuật viết code sạch và dễ đọc dành cho người mới bắt đầu

HaDu Đặng
LecleVietnam
Published in
10 min readApr 17, 2019

Viết code là một chuyện, nhưng để viết được code “sạch” và dễ đọc lại là chuyện khác. Nhưng code “sạch” là gì? Tôi đã tạo ra một đoạn code ngắn để làm hướng dẫn cho người mới bắt đầu về cách mà bạn có thể hiểu và nắm bắt nghệ thuật viết code “sạch”.

Hãy tưởng tượng bạn đang đọc một bài báo. Có một đoạn mở đầu cho bạn cái nhìn tổng quan ngắn gọn về bài báo. Có tiêu đề, mỗi tiêu đề có một loạt các đoạn văn. Các đoạn văn được cấu trúc và nhóm lại theo những thông tin có liên quan và được sắp xếp sao cho flow của bài viết cực kỳ trôi chảy và dễ đọc.

Bây giờ, hãy tưởng tượng một bài viết không có bất kỳ tiêu đề nào. Có nhiều đoạn văn, nhưng chúng dài lượt thượt và được xếp theo một trật tự khó hiểu. Bạn không thể đọc lướt qua bài viết mà phải thực sự đi sâu vào nội dung để cảm nhận về nội dung của bài viết. Điều này có thể khá bực bội!

Code của bạn cũng đọc như một bài viết tốt. Hãy nghĩ về các class/file của bạn như các tiêu đề và các method của bạn như các đoạn văn. Các câu là các statement trong code của bạn. Dưới đây là một số đặc điểm của code “sạch”:

  1. Code “sạch” có tính tập trung —mỗi function, mỗi class và module nên tập trung làm một việc và làm tốt việc đó.
  2. Code “sạch” phải thanh lịch— Code “sạch” cần có tính đơn giản để đọc. Việc đọc chúng cần phải khiến bạn mỉm cười và khiến bạn suy nghĩ “ta đã biết rõ ràng và chính xác đoạn code này dùng dễ làm gì”
  3. Code “sạch” cần được chăm sóc. Ai đó đã bỏ thời gian để khiến cho code trở nên đơn giản và có trật tự. Họ có sự chú ý phù hợp đến từng chi tiết và có sự quan tâm dành cho chúng.
  4. Code “sạch” cần phải pass qua phần test — Code hỏng thì không “sạch”!

Để trả lời cho một câu hỏi lớn trong ngày: làm sao để viết được code “sạch” khi đang là một junior developer? Đây là lời khuyên hàng đầu của tôi để bắt đầu.

Sử dụng định dạng và thụt lề nhất quán

Sách sẽ khó đọc nếu cách dòng không nhất quán, kích thước phông chữ khác nhau và dấu ngắt dòng ở khắp mọi nơi. Code của bạn cũng vậy.

Để làm cho code của bạn rõ ràng và dễ đọc, hãy đảm bảo rằng thụt lề, ngắt dòng và định dạng là nhất quán. Đây là một ví dụ tốt và xấu:

Ví dụ tốt

function getStudents(id) { 
if (id !== null) {
go_and_get_the_student();
} else {
abort_mission();
}
}
  • Thoáng nhìn, bạn sẽ nhận ra có một câu lệnh if/else trong hàm
  • Dấu ngoặc và thụt đầu dòng nhất quán giúp dễ dàng xem nơi các code block bắt đầu và kết thúc
  • Dấu ngoặt nhất quán— Để ý cách mà dấu ngoặt bắt đầu cho functionvà choif là trên cùng một dòng.

Ví dụ xấu:

function getStudents(id) {
if (id !== null) {
go_and_get_the_student();}
else
{
abort_mission();
}
}

Chà! Có rất nhiều lỗi ở đây!

  • Thụt lê khắp mọi nơi, bạn không thể biết được nơi function kết thúc, hoặc nơiif/else bắt đầu(vâng, có một if/else block trong đoạn code này!)
  • Dấu ngoặc gây khó hiểu và không nhất quán
  • Khoảng cách dòng không nhất quán

Có một chút phóng đại trong ví dụ, nhưng nó cho thấy lợi ích của việc sử dụng thụt lề và định dạng nhất quán. Tôi không biết bạn cảm thấy như thế nào, nhưng với tôi thì ví dụ tốt trông thuận mắt hơn nhiều.

Tin vui là có nhiều plugin IDE bạn có thể sử dụng để tự động định dạngcode cho bạn. Hallelujah!

Sử dụng tên biến và method rõ ràng

Ban đầu, tôi đã nói vềtầm quan trọng của việc viết code dễ đọc. Một khía cạnh lớn của điều này là cách mà bạn đặt tên(đây là một trong những sai lầm tôi đã mắc phải khi tôi là một junior developer). Hãy xem một ví dụ về cách đặt tên hay:

function changeStudentLabelText(studentId){                  
const studentNameLabel = getStudentName(studentId);
}
function getStudentName(studentId){
const student = api.getStudentById(studentId);
return student.name;
}

Đoạn code này khá tốt ở nhiều mặt:

  • Các function được đặt tên rõ ràng với các argument được đặt tên rất tốt. Khi một developer đọc đoạn code này, mọi thứ sẽ nhanh chóng rõ ràng với họ, “Nếu ta gọigetStudentName() method với mộtstudentId, ta sẽ nhận về tên của một sinh viên (student name)" — Họ không cần phải điều hướng đếngetStudentName() method nếu như họ không cần!
  • Trong getStudentName() method, các phép gọi biến và method một lần nữa cũng được đặt tên rất tốt- Thật dễ để nhận biết rằng method đang gọiapi, lấy một student object, và trả về mộtname property. Dễ dàng!

Chọn tên tốt khi viết code “ sạch” cho người mới bắt đầu khó hơn bạn nghĩ. Khi ứng dụng của bạn phát triển, hãy sử dụng các quy ước này để đảm bảo code của bạn dễ đọc:

  • Chọn một style đặt tên và hay thật nhất quán. Có thể làcamelCasehoặc under_scores nhưng không phải là cả hai!
  • Đặt tên cho function, method, và biến của bạn bằng những thứ chúng làm hoặc mô tả chúng là gì. Nếu method của bạn lấy một thứ gì đó, hay thêm get vào trong tên. Nếu biến của bạn lưu trữ (store) màu của xe hơi, gọi nócarColour, ví dụ vậy.

Tip bổ sung: Nếu bạn không thể đặt tên cho function của mình, điều đó có nghĩa là nó đang phải làm nhiều thứ một lúc. Hãy tiếp tục và chia nó thành những function nhỏ hơn! Ví dụ, nếu bạn kết thúc với việc gọi function của mình làupdateCarAndSave(), hãy tạo ra 2 functionupdateCar()saveCar().

Sử dụng comment khi cần thiết

Có một câu nói, code cần phải self-documenting, điều đó có nghĩa là, thay vì phải sử dụng thật nhiều comment, code của bạn nên đọc đủ tốt để làm giảm đi nhu cầu sử dụng comment. Đây là một điểm có giá trị, và tôi đoán điều này sẽ có ý nghĩa trong một thế giới hoàn hảo. Tuy nhiên, thế giới của những đoạn code là không hoàn hảo, vì vậy đôi khi cần phải dùng đến comment.

Các comment tài liệu (Docummentation Comment) là những comment mô tả những gì một function hay một class cụ thể làm. Nếu bạn đang viết một thư viện, điều này sẽ hữu ích cho các developer đang sử dụng thư viện của bạn. Dưới đây là một ví dụ từ useJSDoc:

/** * Solves equations of the form a * x = b 
* @example *
// returns 2 * globalNS.method1(5, 10);
* @example *
// returns 3 * globalNS.method(5, 15);
* @returns {Number} Returns the value of x for the equation. */ globalNS.method1 = function (a, b) { return b / a; };

Các comment làm rõ (clarification comment) có thể làm rõ một vấn đề nào đó cho bất kỳ ai (bao gồm cả bạn trong tương lai), những người có thể cần duy trì, tái cấu trúc hoặc mở rộng code của bạn. Hầu như ta có thể tránh đi các comment làm rõ bằng self-documenting code. Dưới đây là một ví dụ về một comment làm rõ:

/* This function calls a third party API. Due to some issue with the API vender, the response returns "BAD REQUEST" at times. If it does, we need to retry */ 
function getImageLinks(){
const imageLinks = makeApiCall();
if(imageLinks === null){
retryApiCall();
} else {
doSomeOtherStuff();
}
}

Dưới đây là một số comment và bạn nên thử và tránh đi. Chúng không tạo ra nhiều giá trị, có thể gây hiểu lầm hay đơn giản là làm lộn xộn code của bạn.

Comment dư thừa không tạo ra giá trị:

// this sets the students age 
function setStudentAge();

Comment sai lệch:

//this sets the fullname of the student 
function setLastName();

Comment mang tính hài hước/ xúc phạm:

// this method is 5000 lines long but it's impossible to refactor so don't try 
function reallyLongFunction();

Ghi nhớ nguyên tắc DRY(Don’t Repeat Yourself)

Nguyên tắc DRY được phát biểu như sau:

“Mọi kiến thức đều phải có một đại diện duy nhất, rõ nhất và có thẩm quyền trong một hệ thống”

Ở lớp nghĩa đơn giản nhất, điều này có nghĩa là bạn nên hướng đến việc giảm thiểu số lượng code trùng lặp đang tồn tại (lưu ý: tôi nói là giảm thiểu chứ không phải loại trừ — có một số trường hợp mà code trùng lặp không phải là tận thế).

Code trùng lặp có thể là một cơn ác mộng với những người duy trì và thay thế code. Hãy nhìn vào một ví dụ:

Hãy tưởng tượng bạn đang tạo một ứng dụng web nguồn nhân lực cho khách hàng. Ứng dụng này cho phép quản trị viên thêm người dùng có vai trò vào cơ sở dữ liệu thông qua API. Có 3 vai trò; nhân viên, quản lý và quản trị viên. Hãy cùng xem xét một số chức năng có thể tồn tại:

Cool! Code của chúng ta chạy được và chạy tốt. , Một lúc sau, khách hàng của chúng ta đến và nói:

Chào! Chúng tôi muốn thông báo lỗi hiển thị câu: “there was an error”. Ngoài ra, để thêm phiền toái, chúng tôi muốn thay đổi API endpoint từ /user thành /users. Cảm ơn!

Okay, trước khi bắt tay vào và viết code, hãy lùi lại một chút. Hãy nhớ lại, ở phần đầu của bài viết, khi tôi nói “ code “sạch” nên có tính tập trung, ví dụ, chỉ làm một điều mà làm tốt nó”. Đây chính là điểm khiến đoạn code hiện tại của chúng ta gặp vấn đềnhỏ. Code thực hiện lệnh gọi API và xử lý lỗi được lặp lại — điều đó có nghĩa là chúng ta phải thay đổi code ở 3 vị trí để đáp ứng các yêu cầu mới. Thật là phiền!

Vậy, điều gì sẽ xảy ra nếu chúng ta tái cấu trúc code để chúng có tính tập trung hơn:

Chúng ta đã chuyển logic tạo ra một lệnh gọi API thành method riêng của nó saveUserToDatabase(user) (đó có phải là một tên hay không thì tùy bạn quyết định!) mà các method khác sẽ gọi để lưu user. Bây giờ, nếu chúng ta cần thay đổi logic API một lần nữa, chúng ta chỉ phải cập nhật 1 method. Tương tự như vậy, nếu chúng ta phải thêm một method khác tạo user, phương thức lưu user vào cơ sở dữ liệu thông qua api đã tồn tại. Hurray!

Ví dụ một case tái cấu trúc code dựa trên những gì vừa học

Nhắm mắt lại, tượng tưởng hết mức có thể rằng chúng ta đang xây dựng một ứng dụng máy tính đơn giản(calculator app). Có các function được sử dụng cho phép chúng ta cộng, trừ, nhân và chia tương ứng. Kết quả được đưa ra bàn điều khiển.

Đây là những gì chúng ta có cho đến nay. Hãy xem xem nếu bạn có thể phát hiện ra các vấn đề trước khi tiếp tục:

Các vấn đề mà đoạn code đang gặp phải là gì?

  • Việc thụt lề không nhất quán — không quan trọng loại định dạng thụt lề ta đang dùng là gì, miễn là nó nhất quán.
  • Function thứ 2 có một số comment dư thừa — chúng ta có thể cho biết những gì đang diễn ra bằng cách đọc tên function và code trong function, vậy chúng ta có thực sự cần một nhận xét ở đây không?
  • Các function thứ 3 và thứ 4 không được đặt tên tốt— doStuffWithNumbers() không phải là tên function tốt nhất vì nó không nêu rõ chức năng của mình. (x, y) cũng không phải là biến mô tả — x & y là gì? là function? số? hay là những quả chuối?
  • Các method thực hiện nhiều hơn một việc — thực hiện tính toán, nhưng cũng hiển thị đầu ra. Chúng ta có thể tách logic hiển thị ra một method riêng — theo nguyên tắc DRY

Bây giờ chúng ta sẽ sử dụng những gì chúng tôi đã học được trong bài hướng dẫn viết code “sạch” cho người mới bắt đầu này để cấu trúc lại mọi thứ và code mới của chúng ta trông như sau:

  • Chúng ta đã sửa lại định dạng thụt lề cho nhất quán
  • Điều chỉnh cách đặt tên cho function và biến
  • Bỏ đi những comment không cần thiết
  • Chuyển logic displayOutput() thành một method riêng- Nếu output cần thay đổi gì, ta chỉ cần đổi ở 1 chỗ là đủ.

Chúc mừng! Giờ thì bạn có thể nói về những hiểu biết của mình về cách viết code “sạch” khi phỏng vấn với nhà tuyển dụng và khi viết CV.

Đừng “over clean” code của bạn

Tôi thường thấy các developer thường đi hơi xa với code “sạch”. Hãy cẩn thận, đừng thử và làm sạch code của bạn quá nhiều, vì nó có thể có tác dụng ngược lại, và thực sự làm cho code của bạn khó đọc và bảo trì hơn. Nó cũng có thể có tác động đến năng suất nếu các developer liên tục nhảy giữa nhiều file/ method để thực hiện một thay đổi đơn giản.

Hãy chú ý đến code “sạch”, nhưng đừng suy nghĩ thái quá ở giai đoạn đầu của project. Hãy chắc chắn rằng code của bạn hoạt động và được kiểm tra tốt. Giai đoạn tái cấu trúc là khi bạn thực sự nên nghĩ về cách làm sạch code của mình bằng nguyên tắc DRY,…

Trong hướng dẫn cách viết code “sạch” cho người mới bắt đầu này, chúng ta đã học được cách:

  • Sử dụng định dạng và thụt lềnhất quán
  • Sử dụng tên biến và method rõ ràng
  • Sử dụng comment khi cần thiết
  • Sử dụng nguyên tắc DRY

Nếu bạn cảm thấy thích bài hướng dẫn này, hãy xem qua quyển sách Clean Code: A Handbook of Agile Software Craftsmanship by Robert C Martin. Nếu bạn thật sự nghiêm túc với việt viết code “sạch” và bức phá qua level junior developer, tôi cực kỳ đề cử quyển sách này với bạn.

Thanks for reading!

Nguồn: Freecodecamp

Dịch: Lecle Vietnam

--

--