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
2def 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 :D1
2
3
4
5
6var 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
5if (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
5String 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
3tmp_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 nametmp
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ụ sau1
2
3
4
5for (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[]
và 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 liketmp
,it
, orretval
, 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
6class ClassName {
private:
DISALLOW_EVIL_CONSTRUCTORS(ClassName);
public:
...
};
Marco được định nghĩa như sau:1
2
3
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
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
4var 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
4var 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 tmp
và retval
, 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
5if (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
2LookUpNamesNumbers(&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
8static 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
2var 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
2var $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
hayDownload
. - Tránh các từ chung chung như
tmp
hayretval
, 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ớiCanListenOnPort()
- 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: Packing Information into Names
http://yoursite.com/2020/05/24/Chapter-2-Packing-Information-into-Names/