Readable Code - phần 2

Sau đây là Phần 2 của loạt bài chia sẻ những điểm hay từ cuốn sách The Art of Readable Code. Nếu bạn chưa đọc Phần 1 thì xin đọc ở đây Packing Infomation into Names.

Phần 2: Comment code như thế nào, và Đơn giản hóa vòng lặp và Logic

1. Comment code như thế nào

Mọi developer đều hiểu là khi maintain code, đặc biệt là những đoạn code dài có logic phức tạp mà không có một dòng comment nào thì thật... ngán. Có câu "Văn mình, vợ người" - nghĩa là văn mình viết thì mình luôn thấy hay (chỉ bực sao mà mọi người không thấy cái hay đó, và ý vế sau của câu xin không bàn ở đây). Viết code trong lập trình cũng vậy, code mình viết thì mình thấy sao hay, sao dễ hiểu thế (cần gì phải comment). Nhưng thực tế không phải lúc nào cũng vậy, có những khi ta đọc lại code của chính mình đã viết năm ngoái mà không có comment thì cũng mất thời gian để hiểu nghiệp vụ, logic của source code.

Ngược lại, có thể chúng ta đã từng gặp những đoạn code có comment đầy đủ nhưng không hề dễ hiểu logic của source code xử lý gì. Đó là bởi vì người viết comment chưa tốt. Vậy comment thế nào để người đọc dễ hiểu logic của source code? Có phải dòng code nào, đoạn code nào cũng cần phải comment?

Cái gì không cần comment?

Việc đọc comment cũng tốn thời gian, và những dòng comment cũng tốn không gian file. Có phải mỗi dòng code có một dòng comment ngay phía trên nó là tốt? Ta xét ví dụ dưới đây.

// The class definition for Account
class Account {
    // Constructor
    public Account();
    // Set the profit member to a new value
    void setProfit(double profit);
    // Return the profit from this Account
    double getProfit();
};

Rõ ràng những dòng comment trong đoạn code trên là không hợp lý, là thừa. Bởi vì không mang nhiều hơn một chút thông tin nào so với dòng code nó comment cho, bởi vậy nó không giúp người đọc dễ hiểu logic hơn.

Vậy điểm cần lưu ý ở đây là: Không comment cho những dòng code mà bản thân nó cũng đủ làm người đọc hiểu một cách nhanh chóng rồi.

Đừng comment cho có (comment)

Nghĩa là ta viết comment chỉ gọi là có comment. Đọc comment ấy người đọc chẳng hiểu ý của method, ý người comment là gì.

// Find the Node in the given subtree, with the given name, using the given depth.
public Node findNodeInSubtree(Node subtree, String name, int depth);

Dòng comment trên là một kểu comment cho có. Nó chỉ như là đọc lại đầy đủ tên, argument của method chứ nó không tóm tắt được mục đích của method.

Để cho dòng comment có ý nghĩa hơn ta có thể comment mô tả method như sau.

// Find a Node with the given 'name' or return NULL.
// If depth <= 0, only 'subtree' is inspected.
// If depth == N, only 'subtree' and N levels below are inspected.
public Node findNodeInSubtree(Node subtree, String name, int depth);

Không comment cho tên (hàm) không hay - Tốt nhất là sửa để tên phù hợp hơn.

Lại nhắc lại một chút của Phần 1 cho thấy việc chọn tên hàm, biết đúng (hay) với mục đích của hàm, của biến thật là quan trọng.

// Enforce limits on the Reply as stated in the Request,
// such as the number of items returned, or total byte size, etc.
void cleanReply(Request request, Reply reply);

Mọi dòng comment của đoạn code trên chỉ với mục đích là để giải thích nghĩa của "clean" trong tên method. Điều đó là không tốt, thay vì comment nhiều vậy ta nên thay đổi tên method thành enforceLimitFromRequest. Để rõ ràng hơn ta có thể comment thêm.

// Make sure 'reply' meets the count/byte/etc. limits from the 'request'
void enforceLimitFromRequest(Request request, Reply reply);

Tên của method lúc này được gọi là “self-documenting.”. Một cái tên hay thì tốt hơn comment hay, bởi vì tên method còn được thấy ở nhiều nơi mà nó được sử dụng (gọi).

Tiếp theo là một ví dụ nữa về việc comment cho tên method không hay.

// Releases the handle for this key. This doesn't modify the actual registry.
void deleteRegistry(RegistryKey key);

Ta thấy ngay từ "delete" mang lại cảm giác bất an cho người đọc. Phải chăng method này xóa một Registry của hệ điều hành? Không phải vậy. Dòng comment đã cố gắng giải thích điều khiến người đọc lo lắng.

Bao gồm “Director Commentary”

Những bộ phim thường có một "Director Commentary" track (tạm gọi là Đạo diễn chú giải) là nơi mà nhà sản xuất film giải thích cho khán giả rằng lý do bộ phim được sản xuất. Điều đó có nghĩa là bạn cũng có thể lồng những comment giải thích tại sao đoạn code lại được viết như vậy, tai sao công cụ này, công nghệ này được sử dụng ở đây.

// Surprisingly, a binary tree was 40% faster than a hash table for this data.
// The cost of computing a hash was more than the left/right comparisons.

Đoạn comment trên đã "dạy" người đọc điều gì đó (duyệt cây nhị phân thì nhanh hơn khoảng 40% duyệt hash table) và chặn đứng ý định tối ưu hóa của maintainer (bằng cách dùng hash table thay cho binary tree).

Thêm những ví dụ về "Director Commentary"

// This heuristic might miss a few words. That's OK; solving this 100% is hard.
...
// This class is getting messy. Maybe we should create a 'ResourceNode' subclass to
// help organize things.

Comment những lỗ hổng trong code của bạn

Trong quá trình phát triển phần mềm đôi khi để đảm bảo tiến độ ta tạm thời chấp nhận những thuật toán, giải pháp xử lý chưa phải là tối ưu nhất. Ta không nên ngại ngùng gì khi để lại những comment cho "lỗ hổng" này. Comment này sẽ rất lợi ích trong làm việc nhóm. Nhờ có comment của ta mà đồng nghiệp của ta sau này dễ dàng lưu tâm cải tiến source code.

Có một số comment "lỗ hổng" đã trở nên phổ biến trong lập trình.

Makeer Nghĩa thông thường
TODO: Sẽ phải làm gì, hoặc không làm gì cả cũng cần comment
FIXME: Như ta biết, code này cần loại bỏ (nhưng ko nhất thiết bây giờ)
HACK: Code thêm vào là không chính thống, nhưng tạm thời giải quyết được vấn đề.
XXX: Nguy hiểm! Đây là vấn đề chính

Sau đây là một số ví dụ tương ứng.

// TODO: How about auto-correcting small spelling errors?
// TODO(dustin): handle other image formats besides JPEG
// FIXME: This won't work if the file is missing.
// FIXME: No hardcode hear.
// XXX: This method badly needs refactoring: should switch by core type. <mbp>

Điều quan trọng nhất của việc comment "lỗ hổng" này là bạn luôn cảm thấy tự do comment theo suy nghĩ của mình là làm sao để source code sẽ được thay đổi trong tương lại. Việc comment này giúp cho người đọc code có cái nhìn sâu sắc về đoạn code, và biết đâu lại gợi mở cho họ ý tưởng tối ưu source code.

2. Đơn giản hóa vòng lặp và logic

Nếu source code mà không có điều kiện If-else, loop, hay một xử lý luồng nào khác thì thì code sẽ vô cùng dễ đọc. Nhưng thực tế thì rất ít source code nào mà đơn giản như thế. Vậy viết code cùng với chúng ra sao cho code dễ đọc? Ý tưởng ở đây là: Làm sao viết điều kiện If-else, loop đó theo luồng xử lý tự nhiên nhất có thể. Viết code làm sao đê người đọc không hải dừng lại đột ngột và không phải đọc lại code của ta.

Thứ tự các argument trong kiểm tra điều kiện

Chúng ta xem hai cách viết kiểm tra điều kiện sau đây cách viết nào dễ đọc hơn, hay hơn.

if (length > 10) {...}
// or
if (10 <= length) {...}

Về mặt ý nghĩa logic thì hai cách viết là như nhau. Nhưng cách viết thứ nhất dễ đọc hơn. Ta xét tiết ví dụ tiếp theo.

while (bytes_received < bytes_expected)
// or
while (bytes_expected > bytes_received)

Vẫn là cách viết thứ nhất hay hơn, dễ đọc hơn. Tại sao vậy? Sau đây là lý giải mà bạn thấy là rất hợp lý.

Phái tay trái Phía tay phải
Chủ thể cần so sánh. Giá trị thay đổi Phó thể để so sánh lại nhiều lần. Thường giá trị không đổi

Thứ tự của if/else block

Khi viết cặp kiểm tra điều kiện if/else chúng ta thường viết một cách ngẫu nhiên thứ tự của chúng. Ví dụ như.

if (a == b) {
 // Case One ...
} else {
 // Case Two ...
}
// or as:
if (a != b) {
 // Case Two ...
} else {
 // Case One ...
}

Có thể bạn không mất thời gian vào việc xác định thứ tự của các kiểm tra trước khi viết code. Nhưng đôi khi chỉ có một thứ tự tốt hơn các thứ tự còn lại. Ta xét ví dụ sau.

if (!url.HasQueryParameter("expand_all")) {
 response.Render(items);
 ...
} else {
 for (int i = 0; i < items.size(); i++) {
 items[i].Expand();
 }
 ...
}

Nghi ta dọc dòng code đầu thì não ta liên tưởng ngay tới trường hợp "exand-all" nhưng code xử lý lại chẳng dính dáng gì đến "Expand" cả. Nó cũng giống như ai đó nói với ta rằn "Không nghĩ đến con voi hồng" thì ta không thể nào ngừng nghĩ về nó. Từ "Không" ở đây dường như tự động bị loại khỏi danh từ "con voi hồng". Vậy ta sẽ sắp xếp lại thứ tự kiểm tra của đọn code trên cho hơp lý như sau.

if (url.HasQueryParameter("expand_all")) {
 for (int i = 0; i < items.size(); i++) {
 items[i].Expand();
 ...
 }
} else {
 response.Render(items);
 ...
}

Thứ tự kiểm tra như trên nó thuận tự nhiên với tư duy não bộ của ta hơn.

Vậy là viết code cũng phần nào giống viết văn. Ta đọc code ta viết thì thấy hay thấy dễ hiểu, nhưng người khác đọc thì không thấy như vậy. Là lập trình viên chúng ta cần hiểu rằng viết code không chỉ là để chạy cho ra kết quả đúng mà hơn nữa viết code, viết comment ra sao để người khác đọc dễ hiểu dễ maintain. Cảm ơn các bạn đã quan tâm.