Chapter 3: Names That Can’t Be Misconstrued

Trong chương trước, chúng ta đã khám phá việc làm sao để đóng gói các thông tin vào tên của bạn. Trong chương này, chúng ta sẽ tập trung vào 1 chủ đề khác: coi chừng những cái tên có thể bị hiểu nhầm

KEY IDEAD

Chủ động xem xét các tên của bạn bằng cách hỏi chính mình “liệu có nghĩa khác có thể làm cho ai đó giải thích từ tên này”

Thực sự bạn nên thử sự sáng tạo ở đây, tích cực tìm ra những “diễn giải sai”. Bước này giúp bạn phát hiện ra những cái tên mơ hồ để từ đó bạn có thể thay đổi chúng.

Example: Filter()

Giả sử bạn đang viết đoạn code dùng để lấy 1 tập kết quả trong database:

1
results = Database.all_objects.filter("year <= 2011")

result bây giờ chứa cái gì?

  • Những đối tượng có năm <= 2011?
  • Những đối tượng có năm không <= 2011?

Vấn đề là filter là 1 từ mập mờ. Nó không rõ ràng như là “to pick out“ (lấy ra) hoặc “to get rid of“ (loại bỏ). Tốt nhất là bạn nên tránh dùng tên filter vì nó dễ gây hiểu nhầm.

Nếu bạn muốn “to pick out”, một cái tên tốt hơn sẽ là select(). Nếu bạn muốn “to get rid of”, một cái tên tốt hơn sẽ là exclude().

Example: Clip(text, length)

Giả sử bạn có 1 hàm giữ thông tin của đoạn văn

1
2
3
# Cuts off the end of the text, and appends "..."
def Clip(text, length):
...

Có 2 cách hiểu bạn có thể tưởng tượng với hành động Clip():

  • Nó sẽ xóa độ dài từ cuối.
  • Nó cắt ngắn text đến độ dài tối đa.

Cách hiểu thử 2 sẽ chiếm đa số nhưng bạn cũng không biết chắc. Để cho sự hoài nghi không còn, bạn có thể sử dụng một cái tên tốt hơn là Truncate(text, length).

Tuy nhiên, tham số tên length cũng bị đổ lỗi. Nếu nó mà max_length, tên đã sẽ rõ ràng hơn.

Nhưng chúng ta vẫn chưa xong. Tên max_length vẫn để lại 1 số cách hiểu khác:

  • Số lượng bytes
  • Số lượng kí tự
  • Số lượng từ

Như bạn đã thấy từ chương trước, đây là 1 trường hợp mà đơn vị nên được thêm vào tên. Trong trường hợp này, chúng ta muốn sử dụng “số lượng kí tự”, do đó thay vì đặt tên là max_length, hãy sử dụng max_chars.

Prefer min and max for (Inclusive) Limits

Ứng dụng mua sẵn của bạn không cho phép người dùng mua quá 10 sản phẩm trong 1 lần shopping

1
2
3
CART_TOO_BIG_LIMIT = 10
if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT:
Error("Too many items in cart.")

Đoạn code này bị 1 lỗi kinh điển off-by-one. Chúng ta có thể dễ dàng sửa nó bằng cách thay thế >= bằng >:
1
if shopping_cart.num_items() > CART_TOO_BIG_LIMIT:

(Hoặc sửa CART_TOO_BIG_LIMIT lên 11). Nhưng vấn đề tận gốc là CART_TOO_BIG_LIMIT là 1 cái tên mơ hồ - nó không rõ ràng là bạn muốn “up to” (lớn hơn hẳn) hay “up to and including” (lớn hơn và bằng).

ADVICE

Cách rõ ràng nhất để đặt tên 1 giới hạn là đặt tên nó max_ hoặc min_ vào trước

Trong trường hợp này, tên nên là MAX_ITEMS_IN_CART. Code mới sẽ đơn giản và rõ ràng:

1
2
3
MAX_ITEMS_IN_CART = 10
if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
Error("Too many items in cart.")

Prefer first and last for Inclusive Ranges

Đây là 1 ví dụ khác khi bạn không thể sử dụng lớn hơn hoặc lớn hơn hoặc bằng

1
2
print integer_range(start=2, stop=4)
# Does this print [2,3] or [2,3,4] (or something else)?

Mặc dù start là 1 cái tên hợp lý nhưng stop lại không hề rõ ràng và có thể có 1 vài cách hiểu.

Đối với các phạm vi bao gồm như vậy (nơi mà phạm vi nên bao gồm cả điểm bắt đầu và điểm kết thúc), một sự lựa chọn tốt với tên first/last. Ví dụ

1
set.PrintKeys(first="Bart", last="Maggie")

Không giống như stop, từ last ở đây đã rõ nghĩa.

Thêm vào đó, first/last, tên min/max cũng có thể hoạt động trong phạm vi bao gồm này miễn là nghĩa phù hợp trong bối cảnh.

Prefer begin and end for Inclusive/Exclusive Ranges

Trong quá trình code, thường xuyên chúng ta gặp các phạm vi bao gồm/loại trừ. Ví dụ, nếu bạn muốn in ra tất cả các sự kiện xảy ra vào 16 tháng 10, chúng ta dễ dàng viết như sau:

1
PrintEventsInRange("OCT 16 12:00am", "OCT 17 12:00am")

Hơn là viết kiểu như:
1
PrintEventsInRange("OCT 16 12:00am", "OCT 16 11:59:59.9999pm")

Do đó một cặp tên tốt với tham số này là gì? Well, theo quy ước của lập trình chung, tên của phạm vi bao gồm/loại trừ là begin/end

Nhưng từ end có 1 chút chưa rõ nghĩa. Ví dụ, trong câu văn “I’m at the end of the book,”, từ “end“ là đã bao gồm. Không may, tiếng anh không có từ nào ngắn gọn thay thế cho cụm từ “just past the last value”

Bởi vì begin/end cũng là quốc ngữ (ít nhất, nó đã được sử dụng trong các thư viện chuẩn cho C++ và hầy hết những nơi mà 1 mảng cần phải được “cắt” theo cách này), nó vẫn là 1 sự lựa chọn tốt nhất.

Naming Booleans

Khi chọn 1 tên cho giá trị biến boolean hoặc 1 hàm sẽ trả về boolean, hãy chắc chắn rằng nó rõ ràng true và false thực sự có nghĩa.

Đây là 1 ví dụ nguy hiểm:

1
bool read_password = true;

Tùy thuộc vào cách bạn đọc nó (không tính chơi chữ), có 2 cách có thể hiểu:

  • Bạn cần đọc password
  • Password đã được đọc

Trong trường hợp này, cách tốt nhất để tránh từ “read” và đặt tên nó là need_password hoặc user_is_authenticated thay thế.

Nói chung, thêm những từ như is, has, can, hoặc should có thể làm cho các giá trị boolean rõ ràng hơn.

Ví dụ, một hàm được đặt tên là SpaceLeft() nhìn như nó sẽ trả về 1 số. Nếu nó có nghĩa là trả về boolean, một cái tên tốt hơn sẽ là HasSpaceLeft().

Cuối cùng, tốt nhất để tránh các từ phủ nhận trong 1 tên. Ví dụ, thay vì

1
bool disable_ssl = false;

Nó nên dễ dàng được đọc hơn nếu nói là
1
bool use_ssl = true;

Matching Expectations of Users: Phù hợp với mong đợi của người dùng

Một vài cái tên gây hiểu nhầm vì người dùng đã có những dự đoán trước về nghĩa của tên đó, mặc dù bạn có thể có nghĩa khác. Trong trường hợp này, cách tốt nhất là chỉ “chịu thua” và thay đổi tên nó để không gây hiểu nhầm.

Example: get*()

Rất nhiều lập trình viên đã từng quy ước rằng phương thức bắt đầu với get sẽ là “lightweight accessors” (dịch tạm là lấy dữ liệu đơn giản, nhanh và nhẹ ý), cái mà đơn giản trả về 1 phần tử bên trong. Đi ngược lại quy ước này có khả năng đánh lừa người dùng đó.

Đây là ví dụ trong java, cái mà không làm vậy

1
2
3
4
5
6
7
8
public class StatisticsCollector {
public void addSample(double x) { ... }

public double getMean() {
// Iterate through all samples and return total / num_samples
}
...
}

Trong trường hợp này, khi thực thi getMean, nó sẽ thực hiện vòng lặp qua các dữ liệu từ trước và tính toán giá trị trung bình. Bước này trả giá rất đắt nếu có rất nhiều dữ liệu. Nhưng 1 lập trình viên không nghĩ ngờ gì có thể gọi getMean() 1 cách bất cẩn, vì nghĩa nó là 1 lời gọi không đắt.

Thay vào đó, phương thức nên được đặt tên như computeMean(), cái mà sẽ nói lên toán tử tốn kém.

Example: list::size()

Đây là 1 ví dụ trong thư viện chuẩn C++. Đoạn code dưới đây là nguyên nhân gây ra rất nhiều bug khó phát hiện, khiến servers của chúng ta chậm hoặc sập:

1
2
3
4
5
6
void ShrinkList(list<Node>& list, int max_size) {
while (list.size() > max_size) {
FreeNode(list.back());
list.pop_back();
}
}

Lỗi ở đây là tác giả không biết list.size() là 1 toán tử O(n) - nó đến qua các node liên kết trong danh sách, thay vì chỉ trả về 1 phép đếm trước, điều này làm cho ShrinkList() là 1 toán tử O(n2).

Đoạn code “đúng” về mặt kỹ thuật và trên thực tế pass tất cả unit test. Nhưng khi ShrinkList() được gọi với 1 dánh sách 1 triệu phần tử, nó sẽ tốn 1 giờ để kết thúc.

Có thể bạn đang nghĩ “Đó là lỗi của người gọi - anh ta hoặc cô ta nên phải đọc document của hàm này 1 cách cẩn thận”. Điều đó đúng nhưng trong trường hợp này, thực tế là list.size không phải là toán tử có thời gian cố định là 1 sự ngạc nhiên. Tất cả các phần khác trong C++ có size là phương thức thời gian cố định.

Nếu size được đặt tên là countSize() hoặc countElements(), thì các lỗi tương tự sẽ ít xảy ra hơn. Người viết thư viện chuẩn của C++ có thể muốn đặt tên phương thức là size() để dùng cho tất cả các phần chưa nó như vector và map. Nhưng vì họ đã làm nnos, lập trình viên dễ dàng mắc lỗi nó là 1 toán tử nhanh, như cách của các containers khác. Rất may, trong thư viện mới nhất của C++, bây giờ thời gian size đã là O(1).

WHO’S THE WIZARD?

A while ago, one of the authors was installing the OpenBSD operating system. During the disk formatting step, a complicated menu appeared, asking for disk parameters. One of the options was to go to “Wizard mode.” He was relieved to find this user-friendly option and selected it. To his dismay, it dropped the installer into a low-level prompt waiting for manual disk formatting commands, with no clear way to get out of it. Evidently “wizard” meant you were the wizard!

Example: Evaluating Multiple Name Candidates

Khi bạn quyết định chọn 1 tên tốt, bạn phải có nhiều thí sinh để cân nhắc.

High-traffic website thường xuyên sử dụng “thí nghiệm” để kiểm tra xem 1 thay đổi trên trang web có cải thiện được kinh doanh hay không? Đây là 1 ví dụ của file cấu hình kiểm soát 1 vài thí nghiệm:

experiment_id: 100
description: “increase font size to 14pt
traffic_fraction: 5%

Mỗi thí nghiệm được định nghĩa bởi 15 cặp thuộc tính/giá trị. Không may, khi bạn định nghĩa 1 thí nghiệm khác tương tự, bạn phải copy và paste lại nhiều dòng:

experiment_id: 101
description: “increase font size to 13pt
[other lines identical to experimentid 100]

Giả sử bạn muốn fix vấn đề này bằng cách đưa ra 1 cách để có 1 thí nghiệm có thể tái sử dụng các thuộc tính cho các thí nghiệm khác (Nó được gọi là mẫu “prototype inheritance”). Kết quả cuối cùng bạn sẽ chỉ gõ như sau:

experiment_id: 101
the_other_experiment_id_I_want_to_reuse: 100
[change any properties as needed]

Câu hỏi đặt ra là: the_other_experiment_id_I_want_to_reuse thực sự nên được đặt tên là gì?

Đây là 4 sự cân nhắc cho tên này:

  1. template
  2. reuse
  3. copy
  4. inherit

Bất cứ tên nào trong số này cũng có nghĩa đối với chúng ta. Nhưng chúng ta phải tưởng tượng rằng, tên nào sẽ ra sao đối với với 1 ai đó đọc code và không biết về chức năng này. Hãy phân tích từng tên, nghĩ ra 1 vài cách ai đó có thể giải thích sai nó:

  1. Xem xét tên template

experiment_id: 101
template: 100

template có 1 vài vấn đề. Đầu tiên, nó không rõ ràng khi nó đang nói rằng “Tôi là 1 bản mẫu” hoặc “Tôi đang sử dụng mẫu khác”. Cách hiểu thứ 2, một “template” thường là thứ gì đó trừu tượng, cái mà phải được “điền vào” trước khi nó thành cụ thể hóa. Ai đó có thể nghĩ 1 thí nghiệm không phải là 1 thí nghiệm “thực sự”.

  1. Còn reuse thì sao?

experiment_id: 101
reuse: 100

reuse là 1 từ okay nhưng với người viết, ai đó có thể hiểu nó đang nói “Thí nghiệm có thể được tái sử dụng hơn 100 lần”. Thay đổi tên thành reuse_id sẽ giúp giải quyết điều này. Nhưng nó lại làm bối rối cho người đọc hiểu reuse_id: 100 nghĩa mà “my id for reuse is 100”.

  1. Hãy cân nhắc về copy:

experiment_id: 101
copy: 100

copy là 1 từ tốt. Nhưng bản thân nó, copy: 100 dường như đang nói rằng “copy thí nghiệm này 100 lần” hoặc “đây là bản copy thứ 100 của cái mèo gì đó”. Để làm nó rõ ràng khái niệm này đang tham chiếu tới 1 thí nghiệm khác, chúng ta có thể đổi tên là copy_experiment. Đây có thể là 1 cái tên tốt nhất cho đến thời điểm này.

  1. Và bây giờ hãy xem xét inherit

experiment_id: 101
inherit: 100

Từ inherit phổ biến với nhiều lập trình viên và nó được hiểu là sửa đổi thêm sau khi được kế thừa. Với lớp kế thừa, bạn sẽ nhận tất cả các phương thức và thành phần của lớp khác và sau đó định nghĩa lại chúng hoặc thêm mới. Thậm chí trong thế giới thực, khi bạn kế thừa tài sản từ người thân, nó cũng được hiểu là mạn có thể bán chúng hoặc sở hữu những thứ khác nữa.

Nhưng quay trở lại vấn đề, hãy làm cho rõ ràng chúng ta đang kế thừa từ thí nghiệm khác. Chúng ta có thêt cải thiện tên này với cái tên inherit_from hoặc thậm chí là inherit_from_experiment_id.

Nhìn chung copy_experimentinherit_from_experiment_id là những cái tên tốt nhất vì chúng mô tả rõ ràng nhất cái mà thực sự đang diễn ra và ít bị hiểu nhầm nhất.

Tổng kết

Những tên tốt nhất chỉ nên có 1 nghĩa để không thể bị hiểu nhầm - người đọc code của bạn sẽ hiểu nó như cách bạn hiểu và không có cách khác. Thiếu may mắn, rất nhiều từ trong tiếng Anh không rõ nghĩa khi chúng ta sử dụng nó trong lập trình như là filter, length, và limit.

Trước khi bạn quyết định 1 cái tên, hãy biện hộ và tưởng tượng rằng tên của bạn có thể bị hiểu nhầm như thế nào? Tên tốt nhất có khả năng chống lại giải thích sai ý nghĩa.

Khi bạn định nghĩa giới hạn trên dưới, max_min_ là những tiền tố tốt. Đối với các phạm vi bao gồm, firstlast là sự lựa chọn. Đối với các phạm vi bao gồm/loại trừ, beginend là tốt nhất vì chúng là quốc ngữ =)).

Khi tên của bạn là 1 boolean, sử dụng những từ như ishas để làm rõ nghĩa nó là boolean. Tránh sử dụng các từ có nghĩa phủ định (ví dụ như disable_ssl)

Coi chừng từ kì vọng của người dùng về những từ nhất định. Ví dụ, người dùng sẽ mong đợi get()size() là các phương thức nhẹ (Beware of users’ expectations about certain words.)

Từ khóa mới: lightweight accessors, lightweight operator => ý nói các toán tử nhẹ và nhanh, không có xử lý logic phức tạp

Tài liệu tham khảo

Author

Ming

Posted on

2020-05-24

Updated on

2024-08-23

Licensed under

Comments