[Code sao cho chuẩn] Bài 3 – Bàn về comment code, bao giờ dừng tranh luận?

         Vấn đề comment code là có lẽ là một trong những vấn đề gây tranh cãi dai dẳng nhất trong giới lập trình. Thuở còn đi học, có lẽ ai trong chúng ta đều nghe thầy giáo dạy bảo hãy comment code đầy đủ, và có lẽ bản thân nhiều người cũng nghĩ như vậy. Thế nhưng sau này khi đi làm hoặc khi có dịp đưa code cho người khác đọc, ta thường được nghe những câu nói kiểu như “viết code như thế này thì comment cái Lol gì cho rắc rối”, “thằng này viết comment gì mà như lừa người”, ..v.v. Hơn thế, còn có những bài viết cổ suý hoặc những lời kinh nghiệm truyền tai nhau có nội dung đại loại là “cảnh giới cao nhất của việc viết code là không cần phải comment và người khác vẫn hiểu”, và rằng nếu code của bạn chưa đạt tới cảnh giới đó thì bạn vẫn còn là … gà, bla .. bla … Có vẻ mâu thuẫn đã bắt đầu xuất hiện rồi nhỉ? Chúng ta sẽ băn khoăn: chúng ta có thức sự cần comment code? Và cuối cùng thì quan điểm của ai là đúng đây?

         Do chịu ảnh hưởng khá nhiều bởi những người thầy dạy mình thuở đại học, quan điểm cá nhân của mình là: chúng ta nên comment code. Tuy nhiên mình sẽ làm rõ thêm những điểm mà chúng ta nên comment và những nơi chúng ta không nên comment. Có như vậy chúng ta mới có một định hướng rõ ràng hơn và một sự đánh giá đúng đắn hơn cho việc comment code, cũng như tránh được những sự nhập nhằng và xung đột giữa các luồng quan điểm trái ngược nhau.

1. Những dòng code chúng ta không nên comment

          Chúng ta biết rằng nếu như chúng ta viết những dòng comment vào code, thì người đọc sẽ mất thời gian để đọc và hiểu nó là gì, việc đọc comment lấy mất đi thời gian quý báu của người đọc (là những lập trình viên vốn dĩ luôn cảm thấy một ngày 24h là quá ngắn), bởi thế nếu việc comment code gây nên những lãng phí thời gian vô ích thì chúng ta nên xem xét lại sự hiện diện của chúng. Xét ví dụ sau đây:

// The class definition for Account
public class Account {
    //Constructor
    Account();

    // Set the profit member to a new value
    public void SetProfit(double profit);

    // Return the profit from this Account
    public double GetProfit();
};

          Đã từng có một khoảng thời gian (hồi còn đi học) mình viết comment kiểu thế này, và thật sự là bây giờ khi đọc nó mình có một cảm giác kiểu như “chỉ muốn đấm vào mặt thằng đã viết”. Bởi vì sao? Bởi vì những comments này quá … ngu =))))

          Những dòng comment ở trên không có một tác dụng nào khác ngoài việc “lãng phí thời gian người đọc một cách vô ích”. Những dòng code đã quá rõ ràng và có thể “hiểu được ngay lập tức” thì tốt nhất là đừng nên comment, để dành cho những dòng code kiểu thế này thì hợp lí hơn:

# remove everything after the second '*'
name = '*'.join(line.split('*')[:2]);

           Mặc dù chúng ta vẫn có thể hiểu được ý nghĩa của những dòng code trên, tuy nhiên sẽ nhanh hơn rất nhiều nếu chúng ta nhận ra ngay được ý tác giả qua dòng comment. Và như tiêu chí mình nói xuyên suốt series này: code nên được viết sao cho người khác dễ hiểu nhất, vậy nên việc dùng một comment ở đây theo mình là hợp lí.

           Tất nhiên là mọi chuyện không chỉ có đơn giản như vậy, việc một số người có quan điểm hạn chế comment cũng bởi nó sẽ khiến người lập trình lười biếng hơn. Chúng ta xét thử thêm một tình huống mà việc comment trở nên không tốt như sau:

// Releases the handle for this key, doesn't modify the actual registry in database.
void DeleteRegistry(RegistryKey* key);

            Nếu như bạn nào từng lập trình qua C++ thì chắc sẽ biết tới khái niệm con trỏ, con trỏ trong C++ không có cơ chế giải phóng bộ nhớ tự động như các ngôn ngữ lập trình cấp cao khác, khi không dùng nữa chúng ta cần phải tự delete con trỏ đi để không gây nên vùng nhớ rác. Ở đây, người lập trình đơn giản chỉ muốn giải phóng vùng nhớ mà con trỏ đang nắm giữ, thế nhưng cách đặt tên hàm thực sự có vấn đề: nó khiến người ta hiểu lầm chức năng của hàm này là xoá 1 Registry ở trong database đi. Để tránh hiểu lầm thì người viết comment một dòng ý nghĩa thực sự cho nó. Điều này gây rất nhiều hiểm hoạ về sau nếu người kế thừa không thực sự hiểu rõ ý nghĩa của nó.

            Thay vì tìm một cách giải quyết tốt hơn, người lập trình lại đưa ra một giải pháp chắp vá là comment cho nó. Trong trường hợp này, tốt hơn là chúng ta không nên comment mà nên sửa lại trực tiếp tên hàm như sau:

void ReleaseRegistryHandle(RegistryKey* key);

         Rõ ràng việc sửa lại tên hàm sẽ làm cho code trở nên sáng sủa hơn rất nhiều. Về mặt này, mình đã từng bàn tới việc đặt tên biến / hàm như thế nào cho hợp (đọc tại đây). Chúng ta nên tránh comment kiểu lấp liếm như ví dụ trên mà hãy tìm cách giải quyết triệt để hơn.

        Tóm lại, chúng ta nên nhớ những trường hợp không nên comment code sau:

- Không comment những đoạn code mà ý nghĩa của nó đã quá rõ ràng.
- Không comment lấp liếm để che giấu vấn đề, hãy giải quyết vấn đề triệt để và rõ ràng hơn.
- Xoá ngay lập tức những comment nếu đã xoá code đi kèm, tránh dư thừa comment.

2. Khi nào chúng ta cần comment code

           Với mình, chúng ta không nên bỏ hẳn không comment, nhưng cũng không nên comment một cách vô tội vạ. Vậy thì câu hỏi đặt ra là: khi nào thì chúng ta cần comment code? Câu trả lời khả dĩ đó là: “comment nên mô tả được suy nghĩ của người viết code và giải đáp đúng được câu hỏi tiềm năng của người đọc”. Comment không chỉ nên là việc giải thích đoạn code này làm gì, mà nó nên mang cả ý nghĩa “tại sao” người viết lại viết như vậy, thậm chí dự đoán trước được những điều mà người đọc sẽ có khả năng thắc mắc trong tương lai. Chúng ta xem xét thử những tình huống sau:

Cần nói rõ ý định tại sao

          Viết code để chương trình hoạt động đúng đã là 1 việc khó, việc làm cho người khác hiểu được ý định của mình đằng sau những đoạn code lại càng khó hơn. Có đôi khi ta có nhiều cách để giải quyết 1 vấn đề, chúng ta cũng nên xem xét việc comment nói rõ “tại sao” ta lại viết đoạn code xử lí như vậy.

         Giả sử ta có 1 đoạn code chạy đa luồng với biến hằng NUMBER_OF_THREADS, giả sử ta đều có thể chạy với giá trị 2, 4 hoặc 8 luồng 1 lúc, tuy nhiên ta lại chọn 4 vì nó phù hợp với cấu hình đại đa số người dùng (không quá ngốn RAM chẳng hạn), khi đó chúng ta cũng nên comment để người đọc hiểu được tại sao ta chọn con số 4.

...
//Can run with value of 2, 4 or 8, but 4 is best for performance
NUMBER_OF_THREADS = 4;
...

Code dễ bị hiểu sai

Thử xét một đoạn code C++ dùng để xoá dữ liệu:

public class Recorder {
.    vector<float> data;
.    ...
.    public void Clear() {
.        vector<float>().swap(data); // Huh? Why not just data.clear()?
}

             Chắc chắn sẽ có người thắc mắc rằng tại sao không dùng lệnh clear() của vector mà lại dùng lệnh swap(), hay hẳn là người viết đã sai điều gì đó ở đây? Không hẳn là thế. Trong C++, thì vector chỉ thực sự được giải phóng vùng nhớ nếu chúng ta dùng lệnh swap(), nó là một đặc điểm của thư viện STL chuẩn mà không phải ai cũng biết. Để tránh hiểu lầm không đáng có, tốt nhất nên thêm vào một comment để người sau khỏi hoang mang:

// Force vector to relinquish its memory (look up "STL swap trick")
vector<float>().swap(data);

Những comment mang lại cái nhìn bao quát

          Nếu bạn đã tham gia một dự án nào đó mà bạn không phải là người phát triển ngay từ đầu, điều khó nhất có thể không chỉ là những hiểu biết mang tính kĩ thuật, mà nó đến từ việc “hiểu được dự án đang làm nó thực sự là cái gì?”. Với một dự án đã được phát triển một thời gian dài, thậm chí tìm được điểm bắt đầu (entry point) của project khi chạy là chỗ nào đã là cả một vấn đề khó khăn rồi. Bạn nghĩ sao nếu với cái đống source code hàng chục file, mỗi file lên tới cả ngàn dòng code, việc đọc code không hề có comment nó khủng khiếp cỡ nào!!!

          Giả sử trong mô hình lập trình web, chúng ta thường dùng mô hình MVC gồm 3 lớp tiêu chuẩn: Models thao tác với dữ liệu, Controller giúp thao tác và điều khiển các nghiệp vụ, View để hiển thị dữ liệu, thế nhưng thực tế mọi chuyện không hề đơn giản như thế. Chúng ta xây dựng thêm các lớp để cache dữ liệu nhằm giảm tải truy vấn xuống DB, chúng ta xây dựng các lớp logic mặt nạ (façade class) để gom nhóm các thao tác ở tầng xử lí thấp hơn, ..v.v.v. Những kĩ thuật nhỏ đó làm code trở nên phức tạp hơn rất nhiều so với mô hình được học, sẽ là tốt hơn nên chúng ta có những comment mang tính bao quát như:

//This file contains helper functions that provide a more
//convenient interface to process IP value.
//It handles accessing permissions and other nitty-gritty details.
class WBN_IP_Logic{

    //hundreds of functions here
    ...

    function isIPv4(){
    }

    function checkValidIP{
    }

    ...
}

Hay kiểu như:

//This class looks complicated, but it’s really
//just a smart cache.
//It doesn’t know anything about the rest of the system
class WBN_Sessions{
    ...

    //thousands lines of code here

    ...
}

           Những dòng comment kiểu như thế này sẽ tiết kiệm cho người đọc rất rất nhiều thời gian so với việc đọc hàng ngàn dòng code để thực sự hiểu được chúng thực sự có ý nghĩa gì. Những dòng code này mang ý nghĩa như bản document mô tả lại ý nghĩa thực sự của những đoạn code mà người dùng đang đọc. Thiếu nó thì chắc hẳn người đọc cũng sẽ vẫn hiểu thôi, tuy nhiên chẳng phải là sẽ tốt hơn nếu người đọc có thể hiểu được nhanh hơn hay sao?

Những đoạn code thực sự khó hiểu

          Mình vẫn nghe người ta nói rằng: “cảnh giới cao nhất của việc viết code là không cần phải comment và người khác vẫn hiểu”, mình không hiểu ý định thực sự của họ khi nói điều này là gì, phải chăng là code hoàn toàn có khả năng dễ đọc như văn viết? Cho tới hiện tại, sau khi cũng đã làm và đọc qua kha khá code của những project từ nhỏ cho tới lớn, mình vẫn không hình dung được cảnh giới đó là gì!!! Phải chăng mình còn gà??? Hay thực sự code không thể nào thực sự dễ đọc như văn viết?

           Nếu ai đã từng code giải quyết những vấn đề thuộc lĩnh vực khoa học máy tính, kiểu như: tracking đối tượng di chuyển trong video, rút trích những luật kết hợp từ dữ liệu mua hàng, v.v.v thì thực sự những thuật toán của nó có những cái rất rất phức tạp, mình chưa tưởng tượng được nếu code mà không có comment chú thích thì làm sao mà người khác có thể hiểu được. Xét ví dụ sau, dò tìm “vùng blob” trong ảnh:

int find_blobs(const vec2Dc& image)
{
    ...
    while (true) {
        struct Blob blob;
        ...
        while (index < blob.elements_number) {
            unsigned int N = (unsigned int)blob.elements_number;
            for (unsigned int i = index; i < N; i++) {
                add_up_neighbour(blob, i);
                add_right_neighbour(blob, i);
                add_down_neighbour(blob, i);
                add_left_neighbour(blob, i);
            }
            index = N;
        }
        remove_blob_from_image(blob);
        ...
    }
}

             Đoạn code trên chỉ là 1 phần nhỏ trong cả hàm hoàn chỉnh, có ai hiểu nó làm gì không? Thực sự nếu không có gợi ý thì có lẽ chẳng ai hiểu nó làm gì (trừ những người đã rất giỏi – số ít không bàn tới). Có những đoạn code mà thực sự bản chất nó đã khó hiểu, vậy nên tốt nhất là ta nên thêm comment để nói rõ ý nghĩa của nó. Cố gắng viết code dễ hiểu là điều đáng hoan nghênh, nhưng nếu không thể, thì chí ít bạn hãy dùng comment để làm rõ ý nghĩa cho nó:

     ...
    //Find blob by comparing kernel
    while (index < blob.elements_number) {
        unsigned int N = (unsigned int)blob.elements_number;
        for (unsigned int i = index; i < N; i++) {}
    }

Tóm lại là gì?

      Tốt hơn cả, mọi người nên nghĩ rằng việc comment code là để giúp cho những người đọc nó biết được thực sự người viết đã viết gì, và để giải quyết điều gì.

     Cần tránh những comment quá vô dụng hoặc lấp liếp:

  • Không comment những đoạn code mà ý nghĩa của nó đã quá rõ ràng.
  • Không comment lấp liếm để che giấu vấn đề, hãy giải quyết vấn đề triệt để và rõ ràng hơn.
  • Xoá ngay lập tức những comment nếu đã xoá code đi kèm, tránh dư thừa comment.

Và chỉ nên comment khi thực sự nó giúp ích cho việc đọc và hiểu code dễ dàng hơn:

  • Comment cho trường hợp có một lí do nào đó đặc biệt (Why, How).
  • Comment cho trường hợp dễ bị hiểu sai
  • Comment miêu tả cho những cái nhìn bao quát.
  • Comment cho những điều thực sự khó hiểu.

     Trên đây là những ý kiến của mình, nếu bạn có ý kiến gì hay thì mời góp ý ở comment phía dưới nhé. Cảm ơn rất nhiều!

Vcttai.

4 thoughts on “[Code sao cho chuẩn] Bài 3 – Bàn về comment code, bao giờ dừng tranh luận?

  1. Có 1 điều nữa mà quên đề cập đến, đó là việc comment nói rõ “tại sao” đoạn code này lại như vậy. Giả sử ta có 1 đoạn code chạy đa luồng với biến hằng NUMBER_OF_THREADS, giả sử ta đều có thể chạy với giá trị 2, 4 hoặc 8 luồng 1 lúc, tuy nhiên ta lại chọn 4 vì nó phù hợp với cấu hình đại đa số người dùng (không quá ngốn RAM chẳng hạn), khi đó chúng ta cũng nên comment để người đọc hiểu được tại sao ta chọn con số 4.

    ...
    //Can run with value of 2, 4 or 8, but 4 is best for performance
    NUMBER_OF_THREADS = 4;
    ...
    

    Like

  2. Pingback: Comment code: viết sao cho CHUẨN? – DLL Blog – Sharing my knowledge

  3. 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