Chapter 6: Making Comments Precise and Compact
Trong chương trước, chúng ta có nói về cái mà chúng ta nên comment (what). Trong chương này, sẽ nói về cách viết comment rõ ràng và gọn nhẹ
Nếu bạn đang định viết 1 comment, nó nên được tóm lược (tỉ mỉ) - cụ thể và chi tiết nhất có thể. Mặt khác, comments cũng chiếm thêm không gian màn hình, mất thời gian để đọc. Do vậy, comment cũng nên được gọn nhẹ
K E Y I D E A
Comments should have a high information-to-space ratio.
Keep Comments Compact
Đây là 1 ví dụ về comment khi định nghĩ kiểu trong C++
1 | // The int is the CategoryType. |
Nhưng tại sao sử dụng 3 dòng để giải thích nó trong khi bạn có thể chú thích, minh họa nó chỉ trong 1 dòng1
2// CategoryType -> (score, weight)
typedef hash_map<int, pair<float, float> > ScoreMap;
Một vài comments cần 3 dòng, nhưng đây không phải là 1 trong số đó.
Avoid Ambiguous Pronouns: Tránh sử dụng đại từ mơ hồ
Tránh sử dụng các từ mà người đọc mấy thêm thời gian để “giải quyết” 1 từ. Và trong 1 vài trường hợp, nó không rõ ràng cái “it” hoặc “this” đang tham chiếu đến. Ví dụ:1
// Insert the data into the cache, but check if it's too big first.
Trong comment này, “it” nên được tham chiếu tới data hoặc cache. Bạn có thể tìm ra câu trả lời bằng cách đọc phần còn lại của mã. Nhưng nếu bạn phải làm vậy, comment ở đây làm gì?
Điều an toàn nhất là điền luôn các thông tin khi có sự mật mờ này. Trong ví dụ trước “it” đang là “the data”:1
// Insert the data into the cache, but check if the data is too big first.
Rất dễ dàng để thay đổi. Bạn cũng có thể đã chỉnh lại cú pháp câu văn để làm “it” rõ ràng hoàn hảo:1
// If the data is small enough, insert it into the cache.
Polish Sloppy Sentences
Trong 1 vài trường hợp, tạo 1 comment chính xác hơn luôn đi đôi với việc làm cho nó nhỏ gọn.
Đây là 1 ví dụ từ web crawler:1
# Depending on whether we've already crawled this URL before, give it a different priority.
Cây này nhìn có vẻ okay, nhưng so sánh thử với phiên bản sau:1
# Give higher priority to URLs we've never crawled before.
Câu văn đơn giản, nhỏ gọn và thẳng thắn. Nó cũng giải thích rằng sự ưu tiên cao hơn là nhận 1 URLs chưa crawled - comment trước đó không chứa thông tin này.
Describe Function Behavior Precisely: Mô tả chính xác hành vi của hàm
Tưởng tượng bạn chỉ viết 1 hàm đếm số dòng của 1 file:1
2// Return the number of lines in this file.
int CountLines(string filename) { ... }
Comment không tỉ mỉ - có rất nhiều cánh để định nghĩa 1 “dòng”. Đây là 1 cái trường hợp liên quan tôi có thể nghĩ tới:
- “” (dòng trống) - 0 hay 1 dòng?
- “hello”: 0 hay 1 dòng?
- “hello\n” - 1 hay 2 dòng?
- “hello\n world” - 1 hay 2 dòng?
- “hello\n\r cruel\n world\r” - 2,3 hay 4 dòng
Sự thực thi đơn giản là đếm số dòng qua kí tự dòng mới (\n) (đây là cách mà lệnh wc của Unix làm việc). Ở đây 1 comment tốt hơn là trùng với sự thực hiện:1
2// Count how many newline bytes ('\n') are in the file.
int CountLines(string filename) { ... }
Comment này không quá dài hơn so với phiên bản trước đó nhưng chứa nhiều thông tin hơn. Nó nói với người đọc rằng hàm sẽ trả về không nếu không có kí tự xuống dòng. Nó cũng nói với người đọc rằng kí tự (\r) cũng không được tính là dòng mới.
Use Input/Output Examples That Illustrate Corner Cases
Khi bạn viết 1 comment, ví dụ lựa chọn 1 input/ouput cẩn thận có thể có thể giá trị như 1 nghìn từ.
Ví dụ, đây là 1 hàm chung xóa đi 1 phần của string:1
2// Remove the suffix/prefix of 'chars' from the input 'src'.
String Strip(String src, String chars) { ... }
Comment này không cụ thể tỉ mỉ vì nó không thể trả lời được câu hỏi như:
- Kí tự này là cả 1 chuỗi con được xóa bỏ hay thực sự chỉ là 1 tập hợp các chữ cái không có thứ tự?
- Chuyện gì sẽ xảy ra nếu có nhiều kí tự chars trong cuối cùng của
src
?
Thay vì đó, một ví dụ với các sự lựa chọn đầy đủ sẽ trả lời các câu hỏi này:1
2
3// ...
// Example: Strip("abba/a/ba", "ab") returns "/a/"
String Strip(String src, String chars) { ... }
Ví dụ chỉ ra đầy đủ chức năng của hàm Strip(). Chú ý rằng 1 ví dụ đơn giản không thực sự hữu ích, nếu bạn không trả lời được những câu hỏi này:1
// Example: Strip("ab", "a") returns "b"
Đây là 1 ví dụ khác để minh họa:1
2
3// Rearrange 'v' so that elements < pivot come before those >= pivot;
// Then return the largest 'i' for which v[i] < pivot (or -1 if none are < pivot)
int Partition(vector<int>* v, int pivot);
Comment này thực sự đủ tỉ mỉ, chi tiết nhưng có 1 chút khó khăn để hình dung. Đây là 1 ví dụ bạn có thể minh họa thêm:1
2
3// ...
// Example: Partition([8 5 9 8 2], 8) might result in [5 2 | 8 9 8] and return 1
int Partition(vector<int>* v, int pivot);
Có 1 vài điểm cần nhắc tới khi chỉ định ví dụ input/ouput chúng ta chọn:
- pivot bằng cách phần tử trong vector để minh họa trường hợp đó.
- Chúng ta đặt sự trùng lặp trong vector (8) để minh họa rằng đây là 1 input được chấp nhận.
- Vector kết quả không được sắp xếp - nếu nó được, người đọc có thể hiểu sai.
- Vì giá trị trả về là 1, chúng ta đảm bảo rằng 1 không phải là 1 giá trị trong vector - cái mà có thể gây nhầm lẫn.
State the Intent of Your Code: Nêu rõ mục đích của bạn
Như chúng ta đã nhắc tới từ chương trước, comment thường nói cho người đọc cái mà chúng ta nghĩ khi chúng ta viết code. Không may, rất nhiều comment cuối cùng chỉ mô tả cái mà code đang làm theo nghĩa đen, mà không có thêm nhiều thông tin hữu ích.
Đây là 1 ví dụ comment như vậy:1
2
3
4
5
6
7void DisplayProducts(list<Product> products) {
products.sort(CompareProductByPrice);
// Iterate through the list in reverse order
for (list<Product>::reverse_iterator it = products.rbegin(); it != products.rend(); ++it)
DisplayPrice(it->price);
...
}
Tất cả những thứ trong comment chỉ đang diễn tả cho dòng bên dưới nó. Thay vào đó, cân nhắc 1 commnent tốt hơn:1
2// Display each price, from highest to lowest
for (list<Product>::reverse_iterator it = products.rbegin(); ... )
Comment này giải thích cái mà chương trình đang làm ở tầng cao hơn. Điều này phù hợp hơn nhiều với những gì lập trình viên đã nghĩ khi cô ấy viết mã.
Một điều thú vị, có 1 bug trong chương trình này! Hàm CompareProductByPrice
đã sắp xếp các items giá cao đầu tiên. Đoạn code đang làm ngược với những gì tác giả dự định.
Đây là 1 lý do tốt tại sao tham số thứ 2 lại quan trọng. Mặc dù bug, comment đầu tiên rất đúng kĩ thuật (vòng lặp không lặp theo thứ tự ngược lại). Nhưng với comment thứ 2, người đọc nhiều khả năng nhận thấy rằng ý đồ của người viết (đầu tiên là đưa ra items giá cao) mâu thuẫn với cách mà code đang làm. Trong thực tế, các comment hoạt động như 1 kiểm tra dự phòng (sẽ được đề cập trong chương 14).
“Named Function Parameter” Comments
Giả sử bạn nhìn thấy 1 hàm như:1
Connect(10, false);
Hàm này gọi cái gì đó bí ẩn với số integer
và boolean
truyền vào.
Trong ngôn ngữ như Python, bạn có thể gán đối số truyền vào bằng tên1
2
3def Connect(timeout, use_encryption): ...
# Call the function using named parameters
Connect(timeout = 10, use_encryption = False)
Không giống như C++ và Java, bạn không thể làm vậy. Tuy nhiên, bạn có thể sử dụng comment inline để có được hiệu quả tương tự:1
2
3void Connect(int timeout, bool use_encryption) { ... }
// Call the function with commented parameters
Connect(/* timeout_ms = */ 10, /* use_encryption = */ false);
Nhận thấy rằng chúng ta đã “đặt tên” cho tham số đầu tiên là timeout_ms
thay vì timeout
. Lý tưởng nhất, đối số của hàm thực sự nên là timeout_ms
, nhưng nếu vì 1 số lý do nào đó chúng ta không thể thay đổi nó, thì đây là 1 cách tiện dụng để “cải thiện” tên.
Khi bạn có đối số boolean
, thực sự quan trọng để đặt /* name = */
trước giá trị. Đặt 1 comment đằng sau giá trị là rất khó hiểu:1
2
3
4// Don't do this!
Connect( ... , false /* use_encryption */);
// Don't do this either!
Connect( ... , false /* = use_encryption */);
Trong ví dụ trên, không rõ ràng là false
là sử dụng mã hóa hay không sử dụng mã hóa.
Đa số các hàm không cần comment như vậy, nhưng nó là một cách tiện dụng (và gọn nhẹ) để giải thích các đối số trông có vẻ bí ẩn.
Use Information-Dense Words: Sử dụng các từ “nặng thông tin”, các từ mọi người lập trình đều hiểu.
Một khi bạn đã lập trình được 1 vài năm, bạn nhận thấy rằng vấn đề và giải pháp chung tương tự xuất hiện nhiều lần. Thường xuyên, sẽ có 1 vài từ hoặc cụm từ được các developers mô tả trong các mẫu/thành ngữ này. Sử dụng những từ này làm comment của bạn ngắn gọn hơn.
Ví dụ giả sử commen của bạn như sau:1
2
3
4// This class contains a number of members that store the same information as in the
// database, but are stored here for speed. When this class is read from later, those
// members are checked first to see if they exist, and if so are returned; otherwise the
// database is read from and that data stored in those fields for next time.
Thay vào đó, bạn chỉ cần nói:1
// This class acts as a caching layer to the database.
Một ví dụ khác, comment như1
2
3
4// Remove excess whitespace from the street address, and do lots of other cleanup
// like turn "Avenue" into "Ave." This way, if there are two different street addresses
// that are typed in slightly differently, they will have the same cleaned-up version and
// we can detect that these are equal.
Có thể thay thế:1
// Canonicalize the street address (remove extra spaces, "Avenue" -> "Ave.", etc.)
Có rất nhiều từ và cụm từ chứa nhiều thông tin có nghĩa, như là “heuristic”, “brute force”, “naive solution” và tương tự. Nếu bạn có 1 comment cảm thấy hơi dài dòng, tìm xem nó có thể được mô tả như 1 vấn đề lập trình kinh điển.
Summary
Chương này thảo luận về viết comment đóng gói nhiều thông tin trong một không gian nhỏ nhất có thể. Đây là 1 vài tips
- Tránh các từ “it”, “this” khi chúng ta có thể tham chiếu tới 1 vài thứ.
- Mô tả hành vi của hàm càng chi tiết thì càng tiện lợi.
- Minh họa comment của bạn với ví dụ lựa chọn input/ouput cẩn thận.
- Nêu ra ý định ở level cao thay vì chi tiết thực hiện bên dưới.
- Sử dụng các comments inline (e.g., Function(/ arg = / … ) ) để giải thích các tham số mơ hồ của 1 hàm.
- Giữ comment của bạn ngắn gọn bằng các từ với nhiều nghĩa.
Chapter 6: Making Comments Precise and Compact
http://yoursite.com/2020/05/24/Chapter-6-Making-Comments-Precise-and-Compact/