[Code sao cho chuẩn] – Bài 2: đặt tên biến, việc đơn giản nhưng bạn đã làm đúng?

          Chúng ta nên đặt tên biến như thế nào? Thoạt nhìn thì đây có vẻ như là một vấn đề hết sức đơn giản chúng ta không cần phải bận tâm tới, có nhiều người coi việc này dễ tới mức chúc ta không cần phải tốn công sức nói về nó hay cần phải có những bài học về việc đó. Có lẽ đó chỉ là suy nghĩ của những người chưa từng viết code, hay có lẽ là những người viết code chưa nhiều. Nếu bạn viết code đủ nhiều để có những trải nghiệm da dạng, thì chắc hẳn là bạn đã từng trải qua việc vò đầu bứt tóc suy nghĩ cả buổi vẫn không tìm ra 1 cái tên hợp lí cho 1 cái biến vớ vẩn nào đó – khi mà bạn đã đặt 10 cái tên biến khác trong phạm vi đó, hay bạn đã trải qua việc mất cả ngày trời để debug những lỗi tưởng chừng bự như con voi trong khi nó đơn giản chỉ là việc dùng sai hàm (có thể là của bạn hoặc người khác viết) bởi bạn đã nhầm lẫn nội dung xử lí của nó với cái tên “chẳng mấy ăn nhập”. Vậy mới thấy tên biến đôi khi ảnh hưởng nhiều tới việc viết code như thế nào. Chúng ta sẽ cùng bàn luận để đưa ra những quy tắc làm cho việc này trở nên dễ dàng và rõ ràng hơn nhé.

         Nói tới việc đặt tên, chúng ta có những đối tượng cần được đặt tên như: tên biến, tên hàm, tên lớp ..v.v. Cũng giống như việc đặt tên các đối tượng khác trong đời sống hằng ngày, tên không được quá phức tạp và dài để khiến nó trở nên khó nhớ, nhưng cũng không được quá đơn giản để khiến nó trở nên mơ hồ và không còn giữ được tính dễ nhận biết. Để làm được điều này, theo mình nghĩ thì chúng ta nên dựa vào 2 tiêu chí sau đây:

- Tiêu chí thứ nhất: Tên phải thể hiện được ý nghĩa và vai trò của nó. 

- Tiêu chí thứ hai: Tên phải rõ ràng và tránh bị hiểu lầm.

        Như mình đã đề cập ở bài trước, tiêu chí “code sao cho dễ hiểu” đóng vai trò rất quan trọng. Do vậy chúng ta sẽ dựa trên 2 tiêu chí này để phân tích và đưa ra những quy tắc đặt tên tối ưu và dễ hiểu nhất. (Mà một lưu ý nhỏ ở đây: cấu trúc của ngôn ngữ lập trình là tiếng Anh, vậy nên chúng ta cũng bàn luận việc đặt tên biến bằng tiếng Anh luôn nhé 🙂 )

1. Tên phải thể hiện được ý nghĩa và vai trò của nó.

packing-info-into-names

          Có lẽ ai trong chúng ta đều hiểu được tiêu chí này, và rằng cái tên mà chúng ta sẽ đặt cho biến phải thể hiện được ý nghĩa của biến, vậy nhưng làm thế nào để chúng ta làm được điều đó, theo mình nghĩ thì có vài ý như sau:

Tránh dùng những từ nhiều nghĩa, hãy dùng những từ có nghĩa cụ thể

       Có những từ mà bản thân nó hàm chứa ý nghĩa không rõ ràng; kiểu như “do”, “prepare”, “get” ..v.v. Nếu chúng ta đặt tên hàm kiểu như “prepareLayout()” hoặc “getLayout()” thì rất khó để truyền tải được ý nghĩa thật sự của nó là gì. Giả sử bạn muốn tải một layout từ internet về để xử lí, thì tốt hơn là bạn nên đặt tên kiểu như “downloadLayout()”, nếu muốn xoá trắng layout trước khi xử lí thì cái tên “clearLayout()” sẽ hợp lí hơn nhiều.

          Một ví dụ khác, giả sử bạn khai báo một lớp xử lí như sau:

class BalanceTree{
    int size;
    …
}

       Vậy bạn muốn truyền tải gì qua biến “size”? Nếu bạn muốn nói về độ cao của cây, hãy dùng từ “height”, nếu muốn nói tới số lượng nodes thì hãy dùng “numNode” … Tóm lại là bạn nên tránh dùng những từ có ý nghĩa chung chung mà thay vào đó là những từ có nghĩa cụ thể hơn.

Chỉ rõ hành vi chứ không phải ý định

        Giả sử bạn viết một hàm nhận tham số đầu vào để điều khiển việc có xuất log vào file hay không, và bạn đặt tên cho tham số đó là “debug”, khi đó hàm bạn viết sẽ có dạng:

function processABC(int param1, int param2, bool debug){
   . . .
   if(debug){
        //export log to file
   }
}

       Bạn quy định nếu debug = TRUE thì xuất log, ngược lại thì không. Thoạt nhìn thì có vẻ như mọi thứ đều ổn, cho tới khi một người khác tiếp nhận code của bạn. Với thời gian ít ỏi cho việc hiểu code của người khác, có thể họ sẽ đủ thời gian để hiểu được “debug” thực sự là làm gì? Là ghi log, là kích hoạt tham số ẩn hay đơn giản là dừng chương trình khi có bất kì warning nào xảy ra? Nếu mục tiêu của bạn đơn giản là muốn ghi log, chẳng phải tên biến “exportLog” rõ ràng hơn nhiều so với “debug” hay sao? Bạn sẽ tránh được nhiều lỗi và tiết kiệm nhiều thời gian cho người khác chỉ với một thay đổi rất nhỏ này. Đoạn code mới sẽ như sau:

function processABC(int param1, int param2, bool exportLog){
   . . .
   if(exportLog){
        //export log to file
   }
}

Chèn thêm những thông tin quan trọng vào tên biến

       Đôi khi thể hiện được mục đích của biến thôi là chưa đủ, chúng ta còn cần có những ràng buộc thêm cho biến, vậy thì chúng ta sẽ làm thế nào để thể hiện những điều này? Câu trả lời là chúng ta nên đính kèm những ràng buộc này vào tên của chúng luôn. Chúng ta thử xem xét một ràng buộc quan trọng sau đây.

       Điều đầu tiên liên quan tới kiểu đơn vị của biến. Giả sử chúng ta có đoạn mã xử lí để tính khoảng thời gian như sau:

var start = (new Date()).getTime();
. . .
var elapsed = (new Date()).getTime() - start;
WriteToScreen( “The processing time is: ” + elapsed + “seconds”);

        Thoạt nhìn thì có vẻ đoạn code trên hoạt động tốt, có điều gì đó không đúng ở đây không? Câu trả lời nằm ở chỗ hàm getTimet() trả về đơn vị là mili giây trong khi ta đang muốn xuất ra  thông tin ở đơn vị giây. Đôi khi đơn vị tính của giá trị đóng vai trò quan trọng trong các xử lí của chúng ta, có thể người lập trình hiểu rõ được giá trị trả về của hàm getTime() là mili giây, tuy nhiên có thể vì vội vàng xử lí hay điều gì đó mà họ quên mất điều này ở kết quả tính cuối cùng.

          Chúng ta có thể tránh được việc quên này đơn giản chỉ bằng việc chỉ rõ đơn vị tính của biến. Chúng ta sẽ dùng “start_ms” thay cho “start”, dùng “elapsed_ms” thay cho “elapsed”. Như vậy code sẽ tường minh và rõ ràng hơn:

var start_ms = (new Date()).getTime();
. . .
var elapsed_ms = (new Date()).getTime() - start_ms;
WriteToScreen( “The processing time is: ” + elapsed_ms / 1000 + “seconds”);

         Vấn đề thứ hai liên quan tới trạng thái của biến. Đôi khi trong cùng một luồng xử lí, chúng ta có những trạng thái khác nhau của biến. Giả sử như chúng ta xử lí việc hiển thị comment của user lên màn hình, để tránh các lỗi liên quan tới security chúng ta sẽ thực hiện việc escape chuỗi, chúng ta có đoạn code như sau:

var comment = DB_processor.getComment();
   . . . //các xử lí có dùng comment (đang ở dạng thô)
comment = escape(comment);
   . . . //các xử lí có dùng chuỗi ở dạng escaped
printToScreen(comment);

         Đoạn code trên sẽ chạy được mà không gặp bất cứ lỗi gì, tuy nhiên có một vấn đề gặp phải nếu đoạn code xử lí trở nên quá dài, một người mới sẽ khó nắm bắt được ý định của chúng ta về việc “escape chuỗi”, và vấn đề có thể phát sinh nếu người đó viết thêm những xử lí khác khi chưa biết rõ được trạng thái của biến. Để giải quyết vấn đề này, chúng ta sẽ đính kèm thông tin thuộc tính của biến vào tên của nó, code sẽ trông như sau:

var plain_comment = DB_processor.getComment();
   . . . // xử lí comment (đang ở dạng thô) mới lấy được
var escaped_comment = escape(comment);
   . . . //xử lí chuỗi ở dạng escaped
printToScreen(escaped_comment);

Vấn đề liên quan tới các biến tạm

        Có một vấn đề mà chúng ta rất hay gặp phải đó là: đặt tên biến tạm, có nhiều kiểu biến tạm khác nhau nhưng ở đây mình sẽ nói về biến tạm dùng trong vòng lặp. Xét đoạn code sau đây:

for (int i = 0; i < clubs.size(); i++)
    for (int j = 0; j < clubs[i].members.size(); j++)
        for (int k = 0; k < users.size(); k++)
            if (clubs[i].members[k] == users[j])
                cout << "user[" << j << "] is in club[" << i << "]" << endl;

        Hầu hết mọi người đều dùng các tên đơn giản như “i”, “j”, “k” để đặt tên biến lặp, điều này không có gì sai cả, tuy nhiên khi có nhiều vòng lặp xử lí cùng 1 lúc, thì có vẻ mọi việc dần trở nên mất kiểm soát. Developer sẽ rất khó để phát hiện các lỗi liên quan tới chỉ số của biến lặp. Bản thân mình đã từng gặp một lỗi liên quan kiểu như thế này, cũng có 3 biến lặp là x, y và z, mình dùng sai chỉ số z ở 1 chỗ duy nhất và code đó chạy sai khiến mình đau đầu suốt tận 1 tháng trời 😦 Do vậy, sẽ tốt hơn nếu ta đính kèm thêm thông tin vào biến lặp để nó rõ nghĩa hơn:

//Dòng code chưa tốt
if (clubs[i].members[k] == users[j])

//Có thể sửa lại như sau:
if (clubs[club_i].members[mem_k] == users[user_j])

//Hoặc sửa thành
if (clubs[ci].members[mk] == users[uj])

       Chỉ với thao tác đơn giản là chỉnh sửa tên của biến, chúng ta đã truyền tải rõ hơn ý định logic của mình tới những người phát triển khác. Một việc nhỏ mà lợi ích không hề nhỏ phải không nào? Chúng ta sẽ tiếp tục nói tới tiêu chí tiếp theo.

2. Tên phải rõ ràng và tránh bị hiểu lầm.

misunderstood.JPG
Cắt bên nào 😀

        Với những tiêu chí được đề cập ở trên, chúng ta đã có thêm rất nhiều thông tin vào tên biến, làm cho chúng trở nên dễ hiểu hơn. Ở phần này chúng ta sẽ nói về khía cạnh khác: cần tránh các trường hợp người đọc code hiểu lầm ý nghĩa của chúng.

        Trong một vài tình huống, việc người đọc code hiểu sai ý của người viết code không phải vì bản thân cái tên biến khó hiểu hay mơ hồ, mà đơn giản chỉ vì người đọc đã có những hình dung khác về nó trước khi họ đọc code. Lấy ví dụ, chúng ta có đoạn code sau:

class Student{
    String name;
    int subjects_score[];

    //Compute and return the student’s avarage score.
    int getAverageScore(){
         // Iterate through array subjects_score and return (total score / num_subjects)
    }
}

         Đoạn code trên đáp ứng tốt các tiêu chí mà chúng ta đã đưa ra ở phần trên, tên biến và tên hàm được đặt gọn gàng và rõ ràng, thế nhưng nó sẽ gặp phải một vấn đề mà chúng ta sẽ xem xét tới sau đây. Hầu hết các lập trình viên đều dùng từ “get” để đặt tên cho hàm có hành động “lấy và trả về một thuộc tính nội tại của lớp”, chỉ đơn giản là “lấy ra” mà không phải tốn chi phí tính toán. Việc chúng ta dùng từ “get” đặt tên cho một hàm có xử lí tính toán dường như đi ngược lại với thói quen của những developer khác, nếu người khác tái sử dụng lại hàm tính điểm trung bình này, rất có thể họ sẽ dùng nó theo một cách có thể ảnh hưởng tới hiệu năng của chương trình, làm chương trình chạy chậm hơn. Xét đoạn code mà một dev khác có thể viết để duyệt qua một chuỗi các học bổng và tìm ra học bổng nào phù hợp với học sinh:

//Cách 1: có độ phức tạp O(n2)
for( int i = 0; i < scolarship[i]; i++){       if( student.getAverageScore() >= scolarship[i].required_score ){
           //get this scolarship
      }
}

//Cách 2: độ phức tạp O(2n)
var student_avarage_score = student.getAverageScore();
for( int i = 0; i < scolarship[i]; i++){       if( student_avarage_score >= scolarship[i].required_score ){
            //get this scolarship
      }
}

        Nếu người lập trình nghĩ rằng việc dùng hàm getAvarageScore() chỉ đơn thuần tốn chi phí O(1) thì rất có thể họ sẽ viết code theo cách đầu tiên, việc này làm ảnh hưởng tới hiệu suất của ứng dụng. Và tất cả chúng ta đều biết, khi chương trình trở nên lớn hơn, thì độ phức tạp O(n2) thực sự trở nên rất tốn tài nguyên.

         Mặc dù không vi phạm quy tắc gì đi nữa thì chúng ta cũng nên tránh điều này. Sẽ tốt hơn nếu chúng ta dùng computeAvarageScore() thay cho getAvarageScore(), chúng ta sẽ chỉ nên dùng “get” để đặt tên cho một hàm không cần tính toán, ví dụ như getStudentName(). Bằng cách thay đổi tên hàm như trên, chúng ta sẽ giúp các lập trình viên khác hiểu chính xác hơn và tránh được các lỗi hiệu suất (performance) tiềm năng có thể xảy ra.

Tóm lại thì chúng ta nên đặt tên biến như thế nào?

         Cảm ơn nếu bạn đã đọc tới đây, chỉ còn vài dòng nữa thôi chịu khó đọc hết luôn nha 🙂

        Tổng kết lại, việc đặt tên biến tưởng chừng đơn giản nhưng chúng ta cũng cần để ý những vấn đề tiềm ẩn có thể xảy ra trong tương lai, các tiêu chí cần nhớ để đặt tên biến có thể được tóm gọn lại như sau:

  - Tiêu chí 1: Tên phải thể hiện được đầy đủ ý nghĩa và vai trò của nó.
      + Tránh dùng từ nhiều nghĩa, hãy dùng những từ có nghĩa cụ thể.
      + Chỉ rõ hành vi thay vì ý định.
      + Chèn thêm những thông tin quan trọng vào tên.
  - Tiêu chí 2: Tên phải rõ ràng và tránh bị hiểu lầm.

Trên đây là một vài quan điểm cá nhân, mọi người thấy có vấn đề cần bàn luận thì cứ comment trao đổi thêm nhé. Cảm ơn đã dành thời gian đọc tới đây 🙂

Vcttai.

Tham khảo:

Sách: Clean Code: A Handbook of Agile Software Craftsmanship

Sách: The art of readable code

6 thoughts on “[Code sao cho chuẩn] – Bài 2: đặt tên biến, việc đơn giản nhưng bạn đã làm đúng?

  1. Mih thường lười suy nghĩ tên biến nên đặt tên biến rất sida, bữa sau đọc lại chả bít mih đã viết j lun :v :v
    Thank u tác giả , ví dụ rất cụ thể và dễ hiểu 🙂

    Like

  2. Pingback: Series viết code sao cho chuẩn: Bài 3 – Bàn về comment code, vô kiếm hay hữu kiếm đây? – Webbynat

  3. Mình làm cho một công ty outsource nên hầu như ngày nào cũng phải đọc source code do người khác viết. Và các trường hợp đặt tên biến chung chung và mơ hồ khiến mình mất nhiều thời gian để dò dẫm theo từng dòng xử lý mới hiểu được các biến đó sử dụng để làm gì.
    Vậy mới thấy việc đặt tên biến tưởng chừng đơn giản nhưng lại quan trọng như thế nào. Việc đặt tên biến tùy tiện không những khiến việc đọc hiểu code trở nên khó khăn cho người khác, mà biết đâu đấy, là cho bản thân người code khi đọc lại code của chính mình sau vài tháng xa cách. 😀
    Bài viết rất bổ ích. Phần tổng kết cuối bài giúp việc nắm bắt và ghi nhớ những ý chính dễ dàng hơn rất nhiều. 🙂
    Mong sẽ sớm có những bài chia sẻ tiếp theo về cách viết code rõ ràng, dễ hiểu.
    Cảm ơn tác giả đã chia sẻ. 😀

    Like

  4. Pingback: [Giới thiệu sách] The art of readable code – Cái tên đã nói lên tất cả – Webbynat

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s