Clean code (P2)
Bài đăng này đã không được cập nhật trong 3 năm
Tiếp nối với bài viết lần trước https://viblo.asia/ngocanh208/posts/57rVRq1OR4bP, bài viết lần này mình sẽ đi cụ thể và chi tiết hơn về các quy tắc viết code "sạch"
Chương 2. Meaningful names
Name are everywhere in software - Tên xuất hiện mọi nơi trong phần mềm: bạn đặt tên cho biến, hàm, danh sách tham số, lớp và gói. Sau đó bạn đặt tên cho tên tệp và tên thư mục chứa chúng, rồi bạn đặt tên tệp nén chúng. Bạn đặt tên, đặt tên và đặt tên. Bạn làm điều đó rất nhiều nên đòi hỏi phải làm thật tốt. Sau đây là một số quy tắc cho việt đặt tên tốt.
2.3. Make meaningful distinctions: Tạo nên sự khác biệt rõ ràng
-
Lập trình viên sẽ tự tạo ra vấn đề khi họ viết mã chỉ để thỏa mãn trình biên dịch hoặc trình thông dịch. Ví dụ, bạn không thể sử dụng cùng một tên để chỉ hai việc khác nhau trong cùng một phạm vi, vì vậy bạn sẽ bị dụ dỗ đổi tên một cách ngẫu nhiên chỉ để thỏa mãn code chạy.
-
Đặt tên cho một dãy số (a1, a2, ..., aN) là ngược lại với việc đặt tên có chủ đích. Những tên này không làm hiểu sai, nhưng chúng cũng không có thông tin. Chúng không cung cấp tư tưởng của tác giả. Ví dụ như
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
-
Hàm này sẽ dễ hiểu hơn khi biến source và destination được sử dụng như tên tham số truyền vào.
-
Những từ nhiễu là một trường hợp khác của tên mà không có sự khác biệt đáng kể. Hãy tưởng tượng rằng bạn có class Product. Nếu bạn có một class khác được gọi ProductInfo hay ProductData, bạn đặt những tên khác nhau mà không tạo ra bất kỳ sự khác biệt nào. Info và Data là hai từ nhiễu.
-
Chú ý quy ước khi sử dụng các tiền tố (như a, an, the) không có vấn đề gì sai bởi chúng tạo nghĩa khác biệt. Ví dụ bạn phải sử dụng a cho tất cả các biến cục bộ và the cho tất cả các tham số của hàm. Bạn sẽ gặp vấn đề khi bạn quyết định gọi tên biến theZork bởi vì đã có một tên biến khác được đặt là zork.
-
Các từ nhiễu là thừa. Một biến không bao giờ nên có tên variable. Từ table không bao giờ nên xuất hiện trong tên bảng. Tên NameString tốt hơn Name ở chỗ nào? Name đã bao giờ là số thực chưa? Nếu như vậy nó phá vỡ quy tắc về sự sai lệch thông tin. Hãy tưởng tượng bạn tìm thấy một tên biến là Customer và một tên biến khác là CustomerObject, bạn sẽ thấy sự khác biệt giữa hai tên này. Tên nào tường minh hơn?
-
Ví dụ, bạn đã cố gắng khắc phục lỗi nhưng nó thực sự là lỗi
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
- Trong trường hợp không có quy ước cụ thể, biến moneyAmount và money là không có sự khác biệt, customerInfo không có sự khác biệt với customer, accountData không có sự khác biệt với account, theMessage không có sự khác biệt với Message. Các tên khác biệt trong trường hợp này giúp bạn biết được sự khác biệt đưa ra.
2.4. Use pronounceable names: Sử dụng tên đọc được
-
Hummans là từ có nghĩa tốt. Một phần quan trọng của bộ nào chúng ta là dành riêng cho các khái niệm về từ. Những từ này được định nghĩa hay phát âm. Sẽ là một sai lầm nếu bạn không tận dụng ưu điểm này của bộ não chúng ta để đối phó với ngôn ngữ nói. Vì vậy tên của bạn được phát âm.
-
Nếu bạn không thể phát âm những cái tên, bạn nên không tìm hiểu và không suy nghĩ về thì bạn chẳng khác gì một tên ngốc.
-
Ví dụ: bạn có thể so sanh hai đoạn mã dưới đây
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
};
với
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
/* ... */
};
Rõ ràng đoạn mã dưới với phiên âm rõ ràng, khi người sử dụng nhìn vào có thể dễ dàng hiểu vấn đề.
2.5. Use searchable names: Sử dụng tên tìm kiếm được
-
Tên biến và tên hằng số có một vấn đề việc xác định vị trí của chúng là không dễ dàng. Nếu biến và hằng được sử dụng nhiều nơi trong đoạn mã, cần tạo ra tên tìm kiếm thân thiện.
-
So sánh
for (int j = 0; j < 34; j++) {
s += (t[j] * 4) / 5;
}
với
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
sum += realTaskWeeks;
}
- Chú ý biến sum không phải là một cái tên đầy đủ hữu ích vì ít nhất vẫn có thể tìm được. Sự cố ý đặt đoạn mã với biến WORK_DAYS_PER_WEEK là dễ dàng tìm thấy hơn con số 5 và đọc tiếp xuống chỉ còn những trường hợp với nghĩa mong muốn.
2.6. Avoid encodings: Tránh trùng bảng mã
-
Bảng mã hóa có sẵn đã đủ dùng vì vậy không bổ sung thêm vào. Mã hóa lạ thêm vào làm tăng thêm gánh nặng cho việc giải mã. Nó là không hợp lý khi yêu cầu nhân viên mới đọc hiểu những mã hóa thêm vào, trong khi những mã hóa có sẵn đã đủ dùng để làm việc.
-
Hungarian Notation: trước đây khi chúng ta làm việc với ngôn ngữ name-length-challenged, chúng ta đã vi phạm những nguyên tắc cần thiết. Vì vậy, Fortran buộc phải mã hóa bằng cách thao tác với các chữ cái đầu tiên. Phiên bản đầu tiên của BASIC cho phép chỉ có một ký tự với một chữ số. Hungarian Notation (HN) đã lên một tầm cao mới. Trong ngôn ngữ lập trình hiện đại, các kiểu dữ liệu phong phú hơn, các trình biên dịch nhớ và thực thi nhiều loại hơn. Hơn nữa, bây giờ mọi người có xu hướng xây dựng các lớp nhỏ hơn và các hàm ngắn gọn hơn vì vậy người ta thường thấy điểm khai báo của mỗi biến mà họ đang sử dụng.
-
Member Prefixes - thành phần tiền tố: bạn không cần phải xác định tên với tiền tố m_ ở mọi nơi. Các lớp và hàm nên đủ nhỏ để không cần phải dùng đến các tiền tố đó. Và bạn nên dùng editing enviroment để đánh dấu hoặc bôi màu cho các lớp hay các hàm để tạo nên sự khác biệt giữa chúng.
- ví dụ
public class Part {
private String m_dsc; // The textual description
void setName(String name) {
m_dsc = name;
}
}
với
public class Part {
String description;
void setDescription(String description) {
this.description = description;
}
}
Bên cạnh đó người ta cũng bỏ tiền tố (hoặc hậu tố) để thấy được phần tên có ý nghĩa hơn.
- Interfaces and Implementations: đôi khi có một số trường hợp đặc biệt khi mã hóa. Ví dụ, nếu bạn đang xây dựng một Abstract Factory để tạo ra đối tượng Shapes. Factory này sẽ được tạo dưới dạng là interface và sẽ được thực thi bởi một lớp cụ thể. Vậy bạn nên đặt tên thế nào cho Factory đó? IShapeFactory hay ShapeFactory? Tôi thích interface ShapeFactory hơn. Khi xác định I phía trước interface, có nhiều phân tán và thông tin không tốt. Vì tôi không muốn khách hàng của tôi biết rằng tôi đang bàn giao cho họ một interface. Tôi chỉ muốn họ biết rằng đó là một ShapeFactory. Do vậy để mã hóa hay thực thi interface tôi chọn sự thực thi (implementation). Gọi đó là ShapeFactoryImp, hoặc thậm chí CShapeFactory hơn là mã hóa interface.
2.7. Avoid mental mapping
-
Class names: Class và object nên có danh từ hoặc cụm danh từ như customer, wikipage, account và addressParser. Tránh dùng những từ như manager, processor, data, infor trong tên của một class. Tên class không nên là một động từ.
-
Method names: tên phương thức nên có động từ hoặc cụm động từ như postPayment, deletePage hoặc save. Các phương thức truy xuất, thay đổi nên được đặt tên cho giá trị của chúng và tiền tố get, set và is theo chuẩn của javabean.
string name = employee.getName();
customer.setName(“mike”);
if (paycheck.isPosted())…
-
Don't be cute - không nền dùng tiếng lóng: Nếu như một cái tên là quá đặc trưng, thường nó sẽ được ghi nhớ với người chia sẻ cảm nhận đó và chỉ có những người này mới nhớ đến những câu chuyện đó. Liệu bạn có biết được hàm HolyHandGrenade có ý nghĩa gì không? Chắc chắn hàm này nó là một tên đặc trưng, trong trường hợp này đăt tên DeleteItems có thể sẽ tốt hơn. Sự lựa chọn tường minh vẫn tốt hơn những giá trị có hàm ý. Ví dụ, không sử dụng whack() để giải nghĩa cho từ kill(), không nên dùng từ đĩa phương để đặt tên như eatMyShort() để giải nghĩa cho abort(). Hãy nói những gì bạn thấy có nghĩa.
-
Pick One Word per Concept - chọn một từ cho mỗi khái niệm: Hãy chọn một từ cho mỗi khái niệm trừu tượng và gắn nó vào. Ví dụ các phương thức tương đương của các lớp khác nhau có các tên fetch, retrieve và get. Làm sao có thẻ nhớ được tên của phương thức trong mỗi lớp? Thật buồn, bạn thường phải nhớ tên công ty, nhóm, hoặc cá nhân nào viết thư viện hoặc lớp để biết tên nào được sử dụng, nếu không bạn sẽ mất khá nhiều thời gian cho việc tìm kiếm. Những IDE hiện đại như Eclipse và Intellij cung cấp những gì có thể dùng được trong các trường hợp đó.
-
Don't Pun - không chơi chữ: Hãy tránh việc dùng một từ cho hai mục đích. Sử dụng một thuật ngữ cho hai mục đích khác nhau cơ bản là trò chơi chữ. Nếu bạn tuân thủ quy tắc "một từ một khái niệm", bạn có thể dùng cho nhiều lớp, ví dụ một phương thức add. Không quan trọng giá trị trả về hoặc tham số truyền vào như thế nào, nhưng phương thức add là tương đương. Tuy nhiên bạn cần cân nhắc sử dụng từ add khi bạn không thêm vào theo cùng nghĩa.
-
Use Solution Domain Names: Nên nhớ rằng những người đọc mã của bạn là những lập trình viên, vì vậy hãy nên dùng những thuật ngữ của khoa học máy tính, thuật toán, toán học ... Tên accountVisitor có ý nghĩa tốt với lập trình viên biết mẫu thieert kế VISISTOR. Việc chọn tên kỹ thuật này là rât hợp lý.
-
Use Problem Domain Names: khi bạn làm việc với người không phải là lập trình ra đoạn mã đó, hãy sử dụng tên nghiệp vụ. Ít nhất người maintain có thể nắm được nghiệp vụ về nghĩa của nó. Tách phần khái niệm và logic là một phần của lập trình viên và thiết kế tốt.
-
Add Meaningful Context: Có rất ít tên mà tự thân nó không có nghĩa, hấu hết là không. Với những tên đó, bạn cần đặt tên vào bối cảnh, bằng việc đặt chúng vào lớp, hàm , gói có tên rõ nghĩa để người đọc dễ hiểu. Khi bạn không làm được điều này thì có thể dùng tiền tố như giải pháp cuối cùng.
Ví dụ 1: biến trong bối cảnh không rõ ràng
private void printGuessStatistics(char candidate, int count) {
String number;
String verb;
String pluralModifier;
if (count == 0) {
number = "no";
verb = "are";
pluralModifier = "s";
} else if (count == 1) {
number = "1";
verb = "is";
pluralModifier = "";
} else {
number = Integer.toString(count);
verb = "are";
pluralModifier = "s";
}
String guessMessage = String.format(
"There %s %s %s%s", verb, number, candidate, pluralModifier
);
print(guessMessage);
}
Ví dụ 2: biến có bối cảnh
public class GuessStatisticsMessage {
private String number;
private String verb;
private String pluralModifier;
public String make(char candidate, int count) {
createPluralDependentMessageParts(count);
return String.format(
"There %s %s %s%s",
verb, number, candidate, pluralModifier );
}
private void createPluralDependentMessageParts(int count) {
if (count == 0) {
thereAreNoLetters();
} else if (count == 1) {
thereIsOneLetter();
} else {
thereAreManyLetters(count);
}
}
private void thereAreManyLetters(int count) {
number = Integer.toString(count);
verb = "are";
pluralModifier = "s";
}
private void thereIsOneLetter() {
number = "1";
verb = "is";
pluralModifier = "";
}
private void thereAreNoLetters() {
number = "no";
verb = "are";
pluralModifier = "s";
}
}
-** Don’t Add Gratuitous Context**: Tên ngắn sẽ tốt hơn tên dài nếu nó có nghĩa rõ ràng. Việc không thêm bối cảnh là cần thiết.
Nguồn: Chương 2. Meaningful Name từ Clean Code https://cleansourcecode.files.wordpress.com/2013/10/clean-code.pdf
All rights reserved