12/08/2018, 14:02

Readable Code - phần 1

Dự án SPEED có điểm khá thú vị là thi thoảng Khách hàng từ Tokyo sang lại mang theo vài cuốn sách về công nghệ, chủ yếu là sách về Programming. Vậy là những lúc dự án ít việc là anh em lại mang sách ra đọc. Mình mới mượn cuốn The Art of Readable Code. Report tháng này mình xin chia sẽ những điểm ...

Dự án SPEED có điểm khá thú vị là thi thoảng Khách hàng từ Tokyo sang lại mang theo vài cuốn sách về công nghệ, chủ yếu là sách về Programming. Vậy là những lúc dự án ít việc là anh em lại mang sách ra đọc. Mình mới mượn cuốn The Art of Readable Code. Report tháng này mình xin chia sẽ những điểm thú vị của cuốn sách với mọi người. Nội dung chính của cuốn sách là chỉ ra những lỗi viết code không theo bất kỳ Convention nào, và từ đó gợi ý cách viết code sao cho ngắn gọn, dễ đọc, dễ hiểu. Mình dự định sẽ viết làm hai phần.

Phần 1: Packing Infomation into Names

Đa phần chúng ta khi mới học lập trình có tâm lý làm mau chóng viết code xong để execute, để được nhìn thấy kết quả. Chúng ta it quan tâm đến chọn tên đặt tên cho biến, đặt tên cho hàm. Rõ ràng đây là sự khởi đầu không tốt dẫn đến thói quen xấu trong viết code khiến ta không trở thành lập trình viên hay.

Có rất nhiều tên biến, tên hàm chúng ta thấy trong chương trình không mang nhiều thông tin, ý nghĩa không rõ ràng. Ví dụ như tmp, get, size.

Phần này mình chia sẻ một số cách thức lựa chọn tên cho biến, hàm làm sao mang được nhiều thông tin, rõ nghĩa, cụ thể từ cuốn sách.

1. Chọn tên biến, hàm mang nghĩa rành mạch, rõ ràng

Sau đây là một ví dụ về việc đặt tên hàm không rõ ràng, rành mạch.

def getPage(url) {
....
}

Từ get trong tên hàm trên mang ý nghĩa khá mơ hồ. Mục đích của hàm là lấy page từ đâu? Từ thư mục, từ database, hay từ Internet? Nếu là từ Internet thì ta có những lựa chọn tên hàm khác rõ nghĩa cho mục đích của hàm hơn đó là fetchPage(), downloadPage().

Hay một ví dụ khác về lớp BinaryTree.

class BinaryTree {
    int size();
    ...
}

Chúng ta mong muốn hàm size() trả về giá trị là gì, số node của cây, độ cao của cây? Vấn đề ở đây là từ size không mang nhiều ý nghĩa. Nếu mục đích của hàm trả về độ cao của cây thì ta nên chọn tên height(), nếu hàm trả về số node của cây thì ta nên chọn tên hàm numberNodes() sẽ mang ý nghĩa cụ thể, rõ ràng hơn.

Thêm một ví dụ nữa về lớp Thread.

class Thread {
    void stop() {
    ...
    }
}

Ở ví dụ trên thì tên hàm stop có vẻ khá ổn, nhưng để ý kỹ hơn đến mục đích của hàm thì ta cũng có thể đưa ra một số tên khác phù hợp hơn. Khi hàm được gọi thì thread sẽ bị hủy bỏ vĩnh viễn không thể thể tiếp tục công việc, thì cái tên kill() sẽ mang ý nghĩa rõ ràng hơn. Hoặc khi hàm được gọi thì thread chỉ bị dừng lại và có thể thực hiện tiếp công việc còn lại sau một thời gian thì cái tên pause() sẽ phù hợp hơn.

Việc đặt tên biến tên hàm mang ý nghĩa chung chung không thể hiện được mục đích của hàm, của biến có thể coi như là một lỗi trong viết code. Lỗi ở đây là làm cho người đọc (có khi ngay chính tác giả của nó) khó hiểu, phải mất nhiều thời gian tìm hiểu code khi maintain code sau này. Vậy làm sao để tránh, để hạn chế mắc lỗi đặt tên như vậy? Trước hết chúng ta luôn suy nghĩ phải chọn tên sao cho thể hiện được ý nghĩa của biến, mục đích của hàm. Chúng ta có thể lựa chọn các từ đồng nghĩa, hoặc hỏi ý kiến của đồng nghiệp gợi ý cho ta những tên phù hợp. Xưa ta học tiểu học được dạy là tiếng Việt giàu và đẹp, nay ta thấy là tiếng Anh đúng là ngôn ngữ giàu và đẹp, nên ta có thể lựa chọn tên phù hợp cho biến, hàm.

Sau đây là một số từ và những từ đồng nghĩa mà ta có thể lựa chọn thay thế cho phù hợp với từng hoàn cảnh cụ thể.

Từ gốc Từ thay thế
send deliver, dispatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, setup, build, gennerate, compose, add, new

2. Loại bỏ những tên biến chung chung như tmp, retval

Khi chúng ta chọn tên biến như tmp, retval, và foo cũng giống như là ta muốn nói rằng "Tôi chẳng nghĩ ra được cái tên nào cả." Thay vào đó ta gắng chọn những tên mà nó miêu tả được giá trị của biến hay mục đích của biến.

Sau đây là ví dụ về vệc sử dụng tên biến retval (return value).

var euclidean_norm = function (v) {
     var retval = 0.0;
     for (var i = 0; i < v.length; i += 1)
     retval += v[i] * v[i];
     return Math.sqrt(retval);
};

Tên biến retval ở ví dụ trên không mang nhiều thông tin, nó không nói nên được mục đích của biến là để làm gì. Để ý thì ta thấy ngay được mục đích của biến retval là tổng các bình phương của các phần tử của mảng v[]. Vậy nên ta chọn tên sum_squares sẽ tốt hơn, lột tả được mục đích của biến, giá trị mà biến chứa đựng trong nó.

Tuy nhiên, trong một số trường hợp tên biến tmp lại lột tả được ý nghĩa của biến. Chúng ta xét ví dụ sau.

if (right < left) {
 int tmp = right;
 right = left;
 left = tmp;
}

Trên là ví đơn giản, mục đích của đoạn logic là hoán đổi giá trị của hai biến right, left cho nhau khi giá trị của right nhỏ hơn left. Trong trường hợp này chọn biến trung gian với tên là tmp thì lại thích hợp. Vì mục đích của biến chỉ là tạm thời ghi giá trị chung gian cho phép hoán đổi mà thôi, sau đó biến không được dùng làm gì khác nữa.

Nhưng ở ví dụ sau thì tên biến tmp được sử dụng lại thể hiện như một sự lười biếng suy nghĩ chọn tên của lập trình viên.

String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
template.set("user_info", tmp);

Ở ví dụ trên mục đích của biến tmp là chứa thông tin của user (giống như tham số của của dòng code cuối), vậy tại sao ta lại chọn tên biến tmp mang ý nghĩa rất chung chung. Đơn giản và mang ý nghĩa cụ thể là biến userInfo.

Tóm lại ta có thể sử dụng tên biến tmp nhưng chỉ trong trường hợp vòng đời bến ngắn (chỉ tồn tại trong khoảng 1 - 3 dòng code), và nó đúng là mang ý nghĩa tạm thời (Temporary) Khi viết vòng lặp lồng nhau chúng ta vẫn thường sử dụng biến i, j, hay iter để duyệt collection. Nếu là vòng lặp đơn thì không sao, nhưng trong trường hợp hai, ba vòng lặp lồng nhau thì việc sử dụng tên biến như vậy trở nên rối ren, dễ nhầm lẫn.

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;

Để ý ta thấy bước kiểm tra điều kiên if thì mảng member[k], và user[j] sử dụng sai biến dẫn đến sai index. Đúng ra dòng code đó phải như sau if (clubs[i].members[k] == users[j]). Trong trường hợp này thay vì chọn tên biến theo thói quen (i, j, k) ta nên chọn tên biến (club_i, mem_j, user_k) hay đơn giản hơn chỉ là (c_i, m_j, u_k) sẽ dễ phân biệt và giảm thiểu sự nhầm lẫn.

Trên đây là một vài chia sẻ về cách sử chọn và đặt tên biến, hàm sao cho rõ nghĩ, cụ thể. Từ đó giúp code dễ đọc, dễ hiểu, dễ maintain hơn.

Hẹn ở phần 2 mình sẽ chia sẻ về cách Comment code, cách Đơn giản hóa vòng lặp và Logic xử lý sao cho code dể đọc.

0