[Clean code] Chapter 2: Đặt tên có ý nghĩa
Tên xuất hiện mọi nơi trong phần mềm. Chúng ta có tên biến, tên hàm, tên đối số, tên lớp, và package. Chúng ta đặt tên cho file mã nguồn, đường dẫn chứa chúng… Chúng ta thực hiện đặt tên, đặt tên và đặt tên. Bởi vì chúng ta làm việc đặt tên rất nhiều nên hãy cố làm nó một cách tốt nhất. Sau đây là một số quy tắc đơn giản để tạo ra tên tốt ^^.
Use Intention-Revealing Names: Sử dụng tên tiết lộ ý định
Thật dễ dàng để nói rằng tên nên thể hiện mục đích. Điều chúng tôi muốn nhấn mạnh với bạn là chúng tôi nghiêm túc về vấn đề này. Việc chọn tên hay tốn thời gian nhưng tiết kiệm được nhiều hơn. Vì vậy, hãy cẩn thận với tên của bạn và thay đổi chúng khi bạn tìm thấy tên hay hơn. Mọi người đọc mã của bạn (kể cả bạn) sẽ vui hơn nếu bạn làm vậy.
Tên của một biến, hàm hoặc lớp phải trả lời được tất cả các câu hỏi lớn. Nó phải cho bạn biết lý do tại sao nó tồn tại, nó làm gì và nó được sử dụng như thế nào. Nếu một tên yêu cầu một bình luận, thì tên đó không thể hiện mục đích của nó.1
int d; // elapsed time in days
Tên d không tiết lộ điều gì cả. Nó không gợi lên cảm giác về thời gian đã trôi qua, hay về ngày tháng. Chúng ta nên chọn một cái tên chỉ rõ cái gì đang được đo lường và đơn vị của phép đo đó:
1 | int elapsedTimeInDays; |
Việc chọn tên thể hiện ý định có thể giúp hiểu và thay đổi mã dễ dàng hơn nhiều. Mục đích của mã này là gì?1
2
3
4
5
6
7public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
Tại sao lại khó để biết đoạn mã này đang làm gì? Không có biểu thức phức tạp nào. Khoảng cách và thụt lề là hợp lý. Chỉ có ba biến và hai hằng số được đề cập. Thậm chí không có bất kỳ lớp hay phương thức đa hình nào, chỉ có một danh sách các mảng (hoặc có vẻ như vậy).
Vấn đề không phải là tính đơn giản của mã mà là tính ẩn dụ của mã (để tạo ra một cụm từ): mức độ mà ngữ cảnh không được nêu rõ trong chính mã. Mã ẩn dụ đòi hỏi chúng ta phải biết câu trả lời cho các câu hỏi như:
theList
là cái quái gì?- Tầm quan trọng cuả x[0], tại sao lại dùng nó để so sánh?
- Dấu hiệu 4 để so sánh mang ý nghĩa gì ?
- Giá trị trả về được sử dụng như thế nào?
Câu trả lời cho những câu hỏi này không có trong code ví dụ, nhưng chúng có thể có. Giả sử chúng ta đang làm việc trong một trò chơi rà phá bom mìn. Chúng ta thấy rằng bảng là một danh sách các ô được gọi là theList
. Hãy đổi tên thành gameBoard
.
Mỗi ô trên bảng được biểu diễn bằng một mảng đơn giản. Chúng ta cũng thấy rằng chỉ số dưới thứ không là vị trí của giá trị trạng thái và giá trị trạng thái là 4 có nghĩa là “đã đánh dấu”. Chỉ bằng cách đặt tên cho các khái niệm này, chúng ta có thể cải thiện đáng kể code:
1 | public List<int[]> getFlaggedCells() { |
Lưu ý rằng tính đơn giản của mã không thay đổi. Nó vẫn có cùng số lượng toán tử và hằng số, với cùng số lượng mức lồng nhau. Nhưng mã đã trở nên rõ ràng hơn nhiều.
Chúng ta có thể đi xa hơn và viết một lớp đơn giản cho các ô thay vì sử dụng một mảng các số nguyên. Nó có thể bao gồm một hàm tiết lộ ý định (gọi là isFlagged
) để ẩn các số ma thuật. Nó tạo ra một phiên bản mới của hàm:
1 | public List<Cell> getFlaggedCells() { |
Với những thay đổi tên đơn giản này, không khó để hiểu điều gì đang diễn ra. Đây chính là sức mạnh của việc chọn tên tốt.
Avoid Disinformation: Tránh thông tin sai lệch
Các lập trình viên phải tránh để lại những manh mối sai lệch làm lu mờ ý nghĩa của mã. Chúng ta nên tránh những từ có ý nghĩa cố hữu khác với ý nghĩa mà chúng ta muốn truyền tải. Ví dụ, hp
, aix
và sco
sẽ là những tên biến kém vì chúng là tên của các nền tảng hoặc biến thể Unix. Ngay cả khi bạn đang mã hóa một cạnh huyền và hp
trông giống như một từ viết tắt hay, thì nó vẫn có thể gây hiểu lầm.
Không gọi một nhóm tài khoản là accountList
trừ khi thực tế đó là một List
. Từ list có nghĩa là một cái gì đó cụ thể đối với các lập trình viên. Nếu vùng chứa các tài khoản không thực sự là List
, thì có thể dẫn đến kết luận sai. Vì vậy, accountGroup
hoặc bunchOfAccounts
hoặc chỉ đơn giản là accounts
sẽ tốt hơn.
Hãy cẩn thận khi sử dụng những cái tên thay đổi theo những cách nhỏ. Phải mất bao lâu để phát hiện ra sự khác biệt tinh tế giữa XYZControllerForEfficientHandlingOfStrings
trong một mô-đun và, ở một nơi nào đó xa hơn một chút, XYZControllerForEfficientStorageOfStrings
? Các từ có hình dạng giống nhau đến đáng sợ.
Việc viết các khái niệm tương tự nhau theo cách tương tự là thông tin. Việc sử dụng các cách viết không nhất quán là thông tin sai lệch. Với các môi trường Java hiện đại, chúng ta tận hưởng việc hoàn thành mã tự động. Chúng ta viết một vài ký tự của một cái tên và nhấn một tổ hợp phím hot (nếu có) và được thưởng bằng một danh sách các cách hoàn thành có thể có cho cái tên đó. Sẽ rất hữu ích nếu các tên cho những thứ rất giống nhau được sắp xếp theo thứ tự bảng chữ cái và nếu sự khác biệt rất rõ ràng, vì nhà phát triển có thể chọn một đối tượng theo tên mà không cần xem các bình luận phong phú của bạn hoặc thậm chí là danh sách các phương thức do lớp đó cung cấp.
Một ví dụ thực sự tệ hại về tên gây hiểu lầm là việc sử dụng chữ L thường hoặc chữ O hoa làm tên biến, đặc biệt là khi kết hợp. Vấn đề, tất nhiên, là chúng trông gần như hoàn toàn giống với hằng số một và không.1
2
3
4
5int a = l;
if ( O == l )
a = O1;
else
l = 01;
Người đọc có thể nghĩ đây là một sự sắp đặt, nhưng chúng tôi đã kiểm tra mã nơi những thứ như vậy rất nhiều. Trong một trường hợp, tác giả của mã đã đề xuất sử dụng một phông chữ khác để sự khác biệt trở nên rõ ràng hơn, một giải pháp sẽ phải được truyền lại cho tất cả các nhà phát triển trong tương lai dưới dạng truyền miệng hoặc trong một tài liệu viết. Vấn đề được khắc phục bằng sự dứt khoát và không tạo ra các sản phẩm công việc mới bằng cách đổi tên đơn giản.
Make Meaningful Distinctions: Tạo ra sự phân biệt có ý nghĩa
Các lập trình viên tự tạo ra vấn đề cho chính họ 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ụ, vì bạn không thể sử dụng cùng một tên để tham chiếu đến hai thứ khác nhau trong cùng một phạm vi, bạn có thể bị cám dỗ thay đổi một tên theo cách tùy ý. Đôi khi điều này được thực hiện bằng cách viết sai chính tả, dẫn đến tình huống đáng ngạc nhiên là việc sửa lỗi chính tả dẫn đến không thể biên dịch.
Không đủ để thêm chuỗi số hoặc từ nhiễu, ngay cả khi trình biên dịch hài lòng. Nếu tên phải khác nhau, thì chúng cũng phải có nghĩa khác nhau.
Đặt tên theo chuỗi số (a1, a2, .. aN)
là ngược lại với đặt tên cố ý. Những cái tên như vậy không phải là không có thông tin — chúng không cung cấp thông tin; chúng không cung cấp manh mối nào về ý định của tác giả. Hãy xem xét:1
2
3
4
5public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
Hàm này đọc tốt hơn nhiều khi source
và destination
được sử dụng cho tên đối số.
Từ nhiễu là một sự phân biệt vô nghĩa khác. Hãy tưởng tượng rằng bạn có một lớp Product
. Nếu bạn có một lớp khác được gọi là ProductInfo
hoặc ProductData
, bạn đã tạo ra các tên khác nhau mà không làm cho chúng có nghĩa khác biệt. Info
và Data
là các từ nhiễu không rõ ràng như a
, an
và the
.
Lưu ý rằng không có gì sai khi sử dụng các quy ước tiền tố như a
và the
miễn là chúng tạo ra sự phân biệt có ý nghĩa. Ví dụ, bạn có thể sử dụng a
cho tất cả các biến cục bộ và the
cho tất cả các đối số hàm. Vấn đề phát sinh khi bạn quyết định gọi một biến theZork
vì bạn đã có một biến khác có tên là zork
.
Từ nhiễu là thừa. Từ variable
không bao giờ được xuất hiện trong tên biến. Từ table
không bao giờ được xuất hiện trong tên bảng. NameString
tốt hơn Name
như thế nào? Name
có bao giờ là số dấu phẩy động không? Nếu có, nó phá vỡ một quy tắc trước đó về thông tin sai lệch. Hãy tưởng tượng tìm thấy một lớp có tên là Customer
và một lớp khác có tên là CustomerObject
. Bạn nên hiểu sự phân biệt này là gì? Cái nào sẽ đại diện cho đường dẫn tốt nhất đến lịch sử thanh toán của khách hàng?
Có một ứng dụng mà chúng tôi biết minh họa điều này. Chúng tôi đã thay đổi tên để bảo vệ người có lỗi, nhưng đây là hình thức chính xác của lỗi:
1 | getActiveAccount(); |
Làm sao các lập trình viên trong dự án này biết được nên gọi hàm nào trong số các hàm này?
Trong trường hợp không có quy ước cụ thể, biến moneyAmount
không thể phân biệt được với money
, customerInfo
không thể phân biệt được với customer
, accountData
không thể phân biệt được với account
và theMessage
không thể phân biệt được với message
. Phân biệt tên theo cách mà người đọc biết được sự khác biệt giữa chúng.
Use Pronounceable Names: Sử dụng tên có thể phát âm được
Con người giỏi về từ ngữ. Một phần đáng kể trong não của chúng ta dành cho khái niệm từ ngữ. Và theo định nghĩa, từ ngữ có thể phát âm được. Sẽ thật đáng tiếc nếu không tận dụng phần lớn não bộ đã tiến hóa để xử lý ngôn ngữ nói. Vì vậy, hãy phát âm tên của bạn.
Nếu bạn không thể phát âm, bạn không thể thảo luận về nó mà không bị coi là một kẻ ngốc. “Ồ, ở đây trên bee cee arr three cee enn tee, chúng ta có một pee ess zee kyew int, thấy không?” Điều này quan trọng vì lập trình là một hoạt động xã hội.
Một công ty tôi biết có genymdhms
(ngày, năm, tháng, ngày, giờ, phút và giây thế hệ) nên họ đi xung quanh và nói “gen why emm dee aich emm ess”. Tôi có thói quen khó chịu là phát âm mọi thứ như đã viết, vì vậy tôi bắt đầu nói “gen-yah-mudda-hims”. Sau đó, nhiều nhà thiết kế và nhà phân tích đã gọi nó như vậy, và chúng tôi vẫn nghe có vẻ ngớ ngẩn. Nhưng chúng tôi đã hiểu trò đùa, vì vậy nó rất vui. Vui hay không, chúng tôi đã chấp nhận việc đặt tên kém. Các nhà phát triển mới phải được giải thích về các biến, và sau đó họ nói về nó bằng những từ ngớ ngẩn được tạo ra thay vì sử dụng các thuật ngữ tiếng Anh đúng. So sánh:
1 | class DtaRcrd102 { |
và1
2
3
4
5
6class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
/* ... */
}
Bây giờ có thể trò chuyện thông minh: “Này, Mikey, hãy xem bản ghi này! Dấu thời gian thế hệ được đặt thành ngày mai! Làm sao có thể như vậy được”
Use Searchable Names: Sử dụng tên có thể tìm kiếm
Tên một chữ cái và hằng số có một vấn đề cụ thể là không dễ để định vị chúng trong một khối văn bản.
Người ta có thể dễ dàng grep cho MAX_CLASSES_PER_STUDENT
, nhưng số 7 có thể gây rắc rối hơn. Các tìm kiếm có thể đưa ra chữ số như một phần của tên tệp, các định nghĩa hằng số khác và trong nhiều biểu thức khác nhau, trong đó giá trị được sử dụng với mục đích khác nhau. Thậm chí còn tệ hơn khi một hằng số là một số dài và ai đó có thể đã chuyển vị các chữ số, do đó tạo ra lỗi trong khi đồng thời trốn tránh tìm kiếm của lập trình viên.
Tương tự như vậy, tên e
là một lựa chọn kém cho bất kỳ biến nào mà lập trình viên có thể cần tìm kiếm. Đây là chữ cái phổ biến nhất trong tiếng Anh và có khả năng xuất hiện trong mọi đoạn văn bản trong mọi chương trình. Về mặt này, tên dài hơn sẽ đánh bại tên ngắn hơn và bất kỳ tên nào có thể tìm kiếm được sẽ đánh bại một hằng số trong mã.
Sở thích cá nhân của tôi là tên một chữ cái CHỈ có thể được sử dụng như biến cục bộ bên trong các phương thức ngắn. Độ dài của tên phải tương ứng với kích thước phạm vi của nó. Nếu một biến hoặc hằng số có thể được nhìn thấy hoặc sử dụng ở nhiều nơi trong một khối mã, điều bắt buộc là phải đặt cho nó một cái tên thân thiện với tìm kiếm. Một lần nữa so sánh:1
2
3for (int j=0; j<34; j++) {
s += (t[j]*4)/5;
}
với1
2
3
4
5
6
7
8int 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;
}
Lưu ý rằng sum
ở trên không phải là tên đặc biệt hữu ích nhưng ít nhất là có thể tìm kiếm được. Mã được đặt tên có chủ đích làm cho hàm dài hơn, nhưng hãy cân nhắc xem việc tìm WORK_DAYS_PER_WEEK
sẽ dễ hơn nhiều so với việc tìm tất cả các vị trí mà 5
đã được sử dụng và lọc danh sách xuống chỉ còn các trường hợp có ý nghĩa mong muốn.
Tránh các ánh xạ về tinh thần (Avoid Mental Mapping)
Nó nên được hiểu như thế nào? Bạn có để ý là trong vòng lặp for()
bạn hay dùng biến i
và j
thậm chí k
để chạy vòng lặp. Có bao giờ bạn dùng biến n
? Hầu như mọi người lập trình đều bị tâm lý rằng biến i,j
để lưu biến trong vòng lặp. Thật là thảm họa khi bạn dùng biến i
lưu tổng số sinh viên của một lớp.
Nói đơn giản hơn, mọi người Việt Nam nhìn chữ “Thị” trong tên là trong đầu hiểu với nhau đưá đó là con gái rồi. Vì vậy nếu bạn là con trai, bạn không nên lấy cái tên “Bùi Thị Hiếu” =))
Tên lớp
Tên lớp nên là danh từ hoặc cụm danh từ như Customer, WikiPage, Account and AddressParser
tránh sử dụng động từ như Manager , Processor , Data , or Info.
Tên phương thức
Tên phương thức hoặc tên hàm nên bắt đầu là động từ, trả lời câu hỏi làm gì với cái gì?. Accessors, mutators, và nhận định nên bắt đầu là get, set và is:1
2
3string name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted())..
Đừng đặt tên “cute”, chơi chữ
Cute và chơi chữ giúp văn của bạn trở nên sinh động cuốn hút người đọc nhưng làm ơn chúng ta đang lập trình, đừng nên đem lối viết văn đó vào việc đặt tên biến. Thay vì tên phương thức đặt là HolyHandGrenade()
hãy đặt là DeleteItem()
, thay vì đặt tên là whack()
, hãy đặt là kill()
, thay vì đặt tên là eatMyShorts()
hãy đặt tên là abort()
Ngữ cảnh trong tên biến
Trong phương thức làm việc với địa chỉ, các tên biến như firstName
, lastName
, street
, houseNumber
, state
có thể làm cho bạn dễ hiểu đó là các biến để lưu thông tin về địa chỉ. Tuy nhiên khi đứng một mình state
liệu bạn có dám chắc là nó để lưu một thông tin về địa chỉ.
Do đó bạn có thể gán các thông tin về context (ngữ cảnh) cho nó để làm cho người đọc thực sự hiểu nó hơn. Bạn có thể thêm context bằng cách sử dụng prefix: addrFirstName, addrLastName, addrState,...
Tổng kết
Tên ngắn hay tên dài không quan trọng, quan trọng là bạn phải đặt tên theo cách dễ đọc, dễ tìm kiếm, dễ maintain code, dễ trao đổi nhất có thể. Hi vọng với một số tips trên giúp các bạn có thể làm điều đó một cách tốt hơn.
Tài liệu tham khảo
- Chapter 2: Clean Code
- http://source-code-wordle.de/
[Clean code] Chapter 2: Đặt tên có ý nghĩa
http://yoursite.com/2019/04/11/Clean-code-Chapter-2-Dat-ten-co-y-nghia/