Chapter 2: Packing Information into Names

Bất cứ khi nào bạn đang đặt tên cho 1 biến, 1 hàm hoặc 1 lớp, có rất nhiều nguyên lý giống nhau được áp dụng. Chúng tôi thích nghĩ về tên như 1 comment nhỏ. Thậm chí nó không xuất hiện ở nhiều chỗ, bạn có thể truyền tải nhiều thông tin bằng cách chọn 1 cái tên tốt.

KEY IDEA
Pack information into your names.

Rất nhiều tên chúng ta nhìn thấy là mơ hồ, như tmp. Thậm chí những từ dường mà chúng ta nhìn có vẻ hợp lý như size hoặc get cũng không đóng gói nhiều thông tin. Chương này sẽ chỉ ra cho bạn làm sao để chọn 1 tên để làm điều đó.

Chương này được tổ chức với 6 chủ đề cụ thể:

  • Chọn 1 từ chuyên biệt
  • Tránh các tên chung chung (hoặc biết khi nào nên sử dụng chúng)
  • Sử dụng các tên cụ thể thay vì các tên trừu tượng.
  • Đính thêm các thông tin vào tên, bằng cách sử dụng 1 hậu tố hoặc tiền tố.
  • Quyết định độ dài của tên nên được sử dụng
  • Sử dụng định dạng tên để đóng gói thêm các thông tin.

Chọn các từ cụ thể, xác định

Một phần đóng góp “đưa thêm thông tin vào tên” là chọn các từ với nghĩa xác định và tránh những từ “trống”. Ví dụ từ Get là rất không rõ nghĩa trong ví dụ:

1
2
def GetPage(url):
...

Từ Get không nói lấy ờ local cache, từ DB hay từ Internet. Nếu lấy từ Internet, một cái tên xác định hơn sẽ nên là FetchPage()or DownloadPage()

Tương tự ví dụ với Size(). Thay vào đó các tên sau sẽ rõ nghĩa hơn Height(), NumNodes(), or MemoryBytes(). Stop cũng nên lấy tên là Pause() or Resume() or Kill().

Tìm các từ có nhiều “màu sắc”

Đừng tiết kiệm sử dụng các từ đồng nghĩa hay nhờ bạn bè gợi ý một cái tên hay ho hơn. Tiếng Anh là một ngôn ngữ đa dạng, tha hồ từ cho bạn lựa chọn để giải quyết các vấn đề của bạn.

Một vài từ trong tiếng Anh “giàu màu sắc” bạn có thể xem xét sử dụng trong ngữ cảnh của mình:

Word Alternatives
send deliver, dispatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new

Trong PHP, có 1 hàm với chuỗi là explode(). Đây là 1 cái tên nhiều màu sắc và nó vẽ ra 1 bước tranh tốt để ngắt 1 cái gì đó vào từng phần, nhưng làm sao để phân biệt được nó với split()? (Hai hàm này là khác nhau nhưng khó để chỉ ra điểm khác nhau giữa chúng dựa vào tên).

KEY IDEA
It’s better to be clear and precise than to be cute.

Tránh các tên chung chung như tmp, retval

Tên kiểu tmp, retval (return value) và foo được hiểu là “Tao không nghĩ ra được tên”. Thay vào đó sử dụng một tên trống như vậy, hãy chọn 1 cái tên mô tả giá trị và mục đích của đối tượng :D

1
2
3
4
5
6
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ạm thời sử dụng retval khi bạn không thể nghĩ được 1 cái tên tốt hơn cho giá trị trả về. Nhưng retval không chứa nhiều thông tin khác ngoài “Tôi là 1 giá trị trả về” (cái mà được sử dụng hiển nhiên ở mọi chỗ).

Một cái tên tốt hơn mô tả mục đích của biến hoặc giá trị nó chứa. Trong trường hợp này, biến là lũy thừa tổng hình vuông của v. Do đó, một cái tên tốt hơn là sum_squares. Điều này cũng thông báo mục đích của biến trước và có thể giúp bạn bắt lỗi nếu có.

Chẳng hạn, hãy tưởng tượng nếu bên trong vòng lặp vô tình:

1
retval += v[i];

Lỗi này sẽ rõ ràng hơn nếu sử dụng tên là sum_squares:
1
sum_squares += v[i]; // Where's the "square" that we're summing? Bug!

ADVICE
The name retval doesn’t pack much information. Instead, use a name that describes the variable’s value.

tmp

Xem xét ví dụ cơ bản về hoán đổi 2 biến:

1
2
3
4
5
if (right < left) {
tmp = right;
right = left;
left = tmp;
}

Trong các trường hợp như vậy, tmp lại hoàn toàn ổn. Mục đích duy nhất của biến là lưu trữ tạm thời, với vòng đời chỉ có 1 vài dòng. Tên tmp truyền đạt ý nghĩa cụ thế với người đọc - đây là biến không có nhiệm vụ khác. Nó không được truyền vào hàm khác hoặc được reset hoặc sử dụng lại nhiều lần.

Nhưng trong trường hợp này, tmp được sử dụng vì sự lười biếng:

1
2
3
4
5
String tmp = user.name();
tmp += " " + user.phone_number();
tmp += " " + user.email();
...
template.set("user_info", tmp);

Mặc dù biến này có vòng đời ngắn, lưu trữ giá trị tạm thời nhưng đây không phải là nhiệm vụ chính, phần quan trọng nhất của nó. Thay vào đó, 1 cái tên như user_info sẽ có tính mô tả hơn

Trong trường hợp sau, tmp nên được sử dụng, nhưng chỉ là 1 phần:

1
2
3
tmp_file = tempfile.NamedTemporaryFile()
...
SaveData(tmp_file, ...)

Nhận thấy rằng, chúng tôi sử dụng biến tmp_file và không chỉ là tmp vì nó là 1 đối tượng file. Tưởng tượng nếu bạn chỉ gọi nó là tmp:
1
SaveData(tmp, ...)

Chỉ nhìn vào 1 dòng code, không rõ ràng là tmp là 1 file, 1 filename hoặc thậm chí có thể là dữ liệu được ghi.

ADVICE
The name tmp should be used only in cases when being short-lived and temporary is the most important fact about that variable.

Vòng lặp

Các biến i,j,k để mô tả cho chúng ta là nó đang ở vòng lặp. Mặc dù các tên này là chung chung nhưng chúng ta sẽ hiểu là “I am an iterator.” (trên thực tế bạn có thể dùng tên này với mục đích khác, nhưng như thế có thể gây nhầm lẫn, đừng làm vậy!!!)

Nhưng đôi khi liệu có tên nào tốt hơn i, j, k. Xem xét ví dụ sau

1
2
3
4
5
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;

Trong khối if members[]users[] đang sử dụng sai chỉ số. Bug kiểu này rất khó tìm bởi những dòng mã có vẻ tốt kiểu như:
1
if (clubs[i].members[k] == users[j])

Trong trường hợp này, sử dụng một cái tên tốt hơn có thể giúp ích. Thay vì chọn (i,j,k) bạn có thể chọn tên khác như (club_i, members_i, users_i) hoặc cô đọng hơn tí thì là (ci , mi , ui ). Khi đó các dòng trên sẽ là
1
if (clubs[ci].members[ui] == users[mi]) # Bug! First letters don't match up. => bug roài

Chúng ta dễ dàng sửa thành:
1
if (clubs[ci].members[mi] == users[ui]) # OK. First letters match.

Quyết định về các tên chung chung

Như bạn đã thấy, một vài trường hợp sử dụng các tên chung chung là hữu ích.

ADVICE
If you’re going to use a generic name like tmp, it, or retval, have a good reason for doing so.

Rất nhiều lần, chúng được lạm dụng cho sự lười biếng. Điều này thì dễ hiểu - khi không có tên nào tốt hơn trong đầu, dễ hơn là chỉ sử dụng các tên không có nhiều ý nghĩa như foo và tiếp tục. Nhưng nếu bạn có 1 thói quen là dành ra vài giây để nghĩ 1 cái tên tốt, bạn sẽ tìm thấy “bắp cơ về đặt tên” của bạn sẽ lên nhanh chóng :D

Ưu tiên các tên cụ thể hơn là các tên trừu tượng

Khi đặt tên cho biến, hàm hoặc các thành phần khác, mô tả nó cụ thể hơn là 1 thứ trừu tượng.

Ví dụ, giả sử bạn có 1 phương thức nội bộ tên là ServerCanStart(), cái mà kiểm tra xem server có thể nghe 1 cổng TCP/IP không? Tên ServerCanStart() là 1 cái gì đó trừu tượng. Một cái tên cụ thể hơn sẽ là CanListenOnPort(). Tên mô tả trực tiếp cái phương thức sẽ làm.

Hai ví dụ tiếp theo minh họa sâu hơn phần này.

Example: DISALLOW_EVIL_CONSTRUCTORS

Đây là ví dụ trong codebase của Google. Trong C++, nếu bạn không định nghĩa một hàm tạo sao chép hoặc toán tử gán cho class của mình, một toán tử mặc định sẽ được cung cấp. Mặc dù tiện dụng, các phương thức này có thể dễ dàng rõ rì bộ nhớ và các rủi ro khác vì chúng thực thi các “behind the scenes” ở nơi mà chúng ta không nhận ra.

Kết quả là, Google có 1 quy ước disallow hàm tạo “tác quái” này, sử dụng 1 marco:

1
2
3
4
5
6
class ClassName {
private:
DISALLOW_EVIL_CONSTRUCTORS(ClassName);
public:
...
};

Marco được định nghĩa như sau:
1
2
3
#define DISALLOW_EVIL_CONSTRUCTORS(ClassName) \
ClassName(const ClassName&); \
void operator=(const ClassName&);

Bằng cách đặt marco này vào private, 2 phương thức này trở thành private do đó bạn không thể sử dụng thậm chí chỉ là vô tình

Tên DISALLOW_EVIL_CONSTRUCTORS không phải là 1 cái tên tốt. Sử dụng từ “evil” là 1 cái tên không rõ ràng và còn gây tranh cãi. Quan trọng hơn, nó không rõ ràng cái gì marco đang disallow. Nó disallows phương thức operator=(), thậm chí không phải là 1 “hàm tạo”!

Tên được sử dụng nhiều năm nhưng cuối cùng đã được thay thế với 1 cái tên ít rõ ràng và nhiều thông tin cụ thể hơn:

1
#define DISALLOW_COPY_AND_ASSIGN(ClassName) ...

Example: –run_locally

Một trong những chương trình có tùy chọn cờ command-line tên là --run_locally. Cờ này sẽ giúp chương trình in ra thêm các thông tin để debugging nhưng chạy chậm hơn. Cờ này thường được sử dụng để testing các máy local, như laptop. Nhưng khi lập trình viên chạy nó ở remote server, hiệu năng là cực kì quan trọng, do đó cờ này không được sử dụng.

Bạn có thể thấy cái tên --run_locally xuất hiện như thế nào, nhưng nó có 1 vài vấn đề:

  • Một thành viên mới của team không biết nó làm gì. Anh ấy sẽ sử dụng nó khi chạy local (giả sử như vậy) nhưng anh ta không biết tại sao cần nó.
  • Thỉnh thoảng, chúng ta cần in các thông tin debugging trong khi chương trình chạy remote. Truyền --run_locally chạy 1 chương trình remote nhìn có vẻ buồn cười, và nó có thể gây nhầm lẫn.
  • Thỉnh thoảng chúng tôi sẽ chạy kiểm tra hiệu năng local nhưng không muốn logging làm chậm nó, do đó chúng tôi không sử dụng được --run_locally.

Vấn đề --run_locally là được đặt tên theo tình huống mà nó thường được sử dụng. Thay vào đó, 1 tên cờ như --extra_logging sẽ trực quan và rõ ràng hơn.

Nhưng điều gì sẽ xảy ra nếu --run_locally cần nhiều hơn chỉ thêm logging? Ví dụ, giả sử nó cần set up va sử dụng các database trên máy local. Bây giờ, tên --run_locally dường như ngon hơn vì nó có thể kiểm soát cả 2 thứ cùng 1 lúc.

Nhưng sử dụng nó cho mục đích đó sẽ là chọn một cái tên vì nó mơ hồ và gián tiếp, có lẽ không phải là một ý tưởng tốt. Giải pháp tốt hơn là tạo một cờ thứ hai có tên --use_local_database. Mặc dù bây giờ bạn phải sử dụng hai cờ, những cờ này nhiều hơn rõ ràng; không cố gắng gộp hai ý tưởng trực giao thành một, và chúng cho phép bạn tùy chọn chỉ sử dụng một ý tưởng chứ không phải ý tưởng khác.

Thêm các thông tin vào tên

Như chúng ta đã nhắc từ phần 1, tên của biến giống như một comment nhỏ.

Giá trị với đơn vị

Nếu biến của bạn là đơn vị đo, thật hữu ích nếu bạn thêm đơn vị vào tên biến.

Ví dụ đo thời gian load 1 trang web:

1
2
3
4
var start = (new Date()).getTime(); // top of the page
...
var elapsed = (new Date()).getTime() - start; // bottom of the page
document.writeln("Load time was: " + elapsed + " seconds");

Không có gì sai trong code nhưng nó sẽ không chạy vì getTime()trả về milisecond, không phải second. Thêm _ms vào tên biến bạn có thể thấy tốt hơn:
1
2
3
4
var start_ms = (new Date()).getTime(); // top of the page
...
var elapsed_ms = (new Date()).getTime() - start_ms; // bottom of the page
document.writeln("Load time was: " + elapsed_ms / 1000 + " seconds");

Thêm các thông tin mã hóa vào thuộc tính (mã hóa bằng gì …)

Kỹ thuật gắn thông tin bổ sung này vào tên không chỉ giới hạn ở các giá trị với các đơn vị. Bạn nên làm điều đó bất cứ lúc nào có một điều gì đó nguy hiểm hoặc đặc biệt về biến số.

Ví dụ, có rất nhiều khai thác bảo mật đến từ việc không nhận ra rằng một số dữ liệu trong chương trình của bạn chưa ở trạng thái an toàn và bạn hoàn toàn có thể cải thiện nó. Để giải quyết nó, bạn có thể sử dụng tên biến như untrustedUrl hoặc unsafeMessageBody. Sau khi gọi hàm để xóa bỏ đầu vào không an toàn, kết quả biến sẽ là trusteUrl hoặc safeMessageBody.

Dưới đây là bảng chỉ ra ví dụ bạn nên thêm các thông tin mã hóa vào tên biến

Situation Variable name Better name
A password is in “plaintext” and should be encrypted before further processing password plaintext_password
A user-provided comment that needs escaping before being displayed comment unescaped_comment
Bytes of html have been converted to UTF-8 html html_utf8
Incoming data has been “url encoded” data data_urlenc

Tuy nhiên bạn không nên sử dụng tên như _utf8, unescaped ở mọi tên biến trong chương trình của bạn. Chúng thực sự quan trọng ở nơi bug do ai đó quên loại bỏ hoặc xử lý bảo mật cho các giá trị.

Tên nên dài bao nhiêu là đủ

Khi bạn chọn 1 tên tốt, có một ràng buộc ngầm định rằng cái tên không nên quá dài. Chẳng ai thích làm việc với cái tên kiểu như newNavigationControllerWrappingViewControllerForDataSourceOfClass. Với một cái tên dài, rất khó để nhớ và chiếm nhiều không gian trên màn hình khiến code càng khó đọc. Mặt khác, các lập trình viên có thể đưa ra lời khuyên này quá xa, chỉ sử dụng tên một từ (hoặc một chữ cái). Vậy bạn nên quản lý sự đánh đổi này như thế nào? Làm thế nào để bạn quyết định giữa việc đặt tên một biến d, ngày hoặc days_since_last_update? Quyết định này là một cuộc gọi phán xét có câu trả lời hay nhất phụ thuộc vào chính xác biến đó là như thế nào đang được sử dụng. Nhưng đây là một số hướng dẫn để giúp bạn quyết định.

Tên ngắn nên được sử dụng trong phạm vi ngắn

Chúng ta đã nhắc tới hạn chế sử dụng biến tmpretval, nhưng không hẳn là chúng ta không được dùng. Với các phạm vi ngắn, chỉ vài dòng code, dùng các biến này vẫn có thể dễ hiểu với các lập trình viên. Xem xét ví dụ sau:

1
2
3
4
5
if (debug) {
map<string,int> m;
LookUpNamesNumbers(&m);
Print(m);
}

Mặc dù biến m không chứa thông tin gì, tuy nhiên nó không hề có vấn đề chi vì người đọc có đầy đủ thông tin họ cần để hiểu đoạn code này. Tuy nhiên nếu m lại là 1 class hoặc là 1 biến global, nhìn kiểu như:
1
2
LookUpNamesNumbers(&m);
Print(m)

Code thật khó đọc, nó không cho ta biết m là cái mèo gì @@. Bởi vậy với các phạm vi rộng, tên cần chứa đủ thông tin cho rõ ràng.

Gõ tên dài - không còn là vấn đề nữa

Có rất nhiều lý do tốt để tránh 1 cái tên dài nhưng “chúng khó gõ hơn” không phải là 1 trong số chúng. Mọi lập trình viên có text editor đã có những gợi ý “tự hoàn thành từ”. Nếu bạn không biết, thì hãy tự tìm hiểu trên text editor của mình nhé.

Từ viết tắt và viết tắt

Lập trình viên có xu hướng dùng các từ viết tắt để tên của họ ngắn lại, ví dụ BEManager thay thế BackEndManager? Liệu nó có thể gây sự nhầm lẫn gì đó không? Theo kinh nghiệm của chúng tôi, viết tắt dành riêng cho dự án thường là một ý tưởng tồi. Chúng xuất hiện khó hiểu và đáng sợ với những người mới vào dự án. Cho đủ thời gian, chúng thậm chí bắt đầu xuất hiện khó hiểu và đáng sợ với các tác giả.

Nguyên tắc để viết tắt là Liệu một người mới vào dự án có hiểu tên của nó không?. Nếu câu trả lời là có, nó có vẻ OK.

Ví dụ, theo chuẩn chung của các LTV thì sử dụng eval viết tắt thay thế evaluation, doc là viết tắt của document, str là viết tắt String. Do vậy nếu 1 thành viên mới nhìn thấy biến FormatStr() hoàn toàn có thể hiểu được nghĩa. Tuy nhiên họ có thể không hiểu nghĩa của biến BEManager.

Vứt bỏ những từ không cần thiết

Đôi khi các từ bên trong tên có thể bị xóa mà không mất bất kỳ thông tin nào cả. Ví dụ ConvertToString có thể thay thế bằng tên ToString hơn mà không làm mất thông tin. Tương tự có thể thay thế DoServerLoop() bằng ServeLoop() cũng rõ ràng.

Sử dụng định dạng tên để truyền đạt ý nghĩa (convention trong tên)

Cách bạn sử dụng dấu gạch dưới, dấu gạch ngang và viết hoa cũng có thể đóng gói thêm thông tin trong một tên. Ví dụ, đây là một số mã C ++ tuân theo các quy ước định dạng được sử dụng cho các dự án nguồn mở của Google:

1
2
3
4
5
6
7
8
static const int kMaxOpenFiles = 100;
class LogReader {
public:
void OpenFile(string local_file);
private:
int offset_;
DISALLOW_COPY_AND_ASSIGN(LogReader);
};

Sử dụng các định dạng code khác nhau cho các đầu vào khác nhau tạo sự khác biệt cho các cú pháp và làm code của bạn dễ hiểu hơn.

Một số loại format tên biến khá phổ biến là CamelCase cho tên lớp và lower_separated cho tên biến nhưng cũng có một số convention khác làm cho bạn ngạc nhiên. (convention công ty :D)

Other Formatting Conventions

Dựa vào ngữ cảnh trong project của bạn, chúng ta có thể có 1 vài convention với các format khác bạn có thể sử dụng để thêm thông tin.

Ví dụ cuốn JavaScript: The Good Parts (Douglas Crockford, O’Reilly, 2008), tác giả có gợi ý “hàm tạo” nên là viết hoa và các hàm thông thường nên bắt đầu bằng chữ thường:

1
2
var x = new DatePicker(); // DatePicker() is a "constructor" function
var y = pageHeight(); // pageHeight() is an ordinary function

Đây là ví dụ khác về JavaScript, khi bạn gọi các hàm trong thư viện jQuery (tên của nó là 1 kí tự $), 1 convention hữu ích là thêm kết quả với $ ở đầu:

1
2
var $all_images = $("img"); // $all_images is a jQuery object
var height = 250; // height is not

Xuyên suốt mã, sẽ rõ ràng $all_images là 1 đối tượng kết quả của jQuery.

Đây là ví dụ cuối cùng, nó nói về HTML/CSS: khi chúng ta nhận vào 1 thẻ HTML tag với 1 thuộc tính id hoặc class, cả 2 kí tự gạch dưới và gạch ngang đều là các giá trị hợp lệ. Một convention có thể thì bạn hãy sử dụng gạch dưới để phân tách các từ trong IDs và gạch ngang để phân tách các từ trong class:

1
<div id="middle_column" class="main-content"> ...

Cho dù bạn quyết định sử dụng các quy ước như thế này là tùy thuộc vào bạn và team của bạn. Nhưng bất cứ hệ thống nào bạn sử dụng, hãy nhất quán trong dự án của bạn.

TÓM LẠI:

Chủ đề duy nhất trong chương này là pack information into your names. Bằng cách này, có nghĩa là người đọc có thể trích xuất rất nhiều thông tin chỉ từ việc đọc tên.

Một vài tips chúng tôi đã giới thiệu:

  • Sử dụng các từ xác định rõ ràng: Get - Fetch hay Download.
  • Tránh các từ chung chung như tmp hay retval, không sử dụng chúng nếu không có lý do đặc biệt.
  • Sử dụng tên cụ thể để mô tả chi tiết hơn 1 cái gì đó tên ServerCanStart() là mơ hồ so với CanListenOnPort()
  • Thêm các thông tin quan trọng vào tên (ví dụ đơn vị)
  • Sử dụng tên dài hơn cho phạm vi rộng hơn. Đừng sử dụng tên 1,2 kí tự cho nhiều màn hình. Tên ngắn tốt nhất là nên chỉ xuất hiện ở 1 vài dòng.
  • Sử dụng các format tạo sự ý nghĩa cho tên :D

Tài liệu tham khảo

Chapter 2

Author

Ming

Posted on

2020-05-24

Updated on

2024-08-08

Licensed under

Comments