Clean Code Series - Part 2: Meaningful Names
Xin chào tất cả mọi người, James Nguyễn quay trở lại với các bạn cùng phần tiếp theo trong series Clean Code mang tên Meaningful Names đây. Cache invalidation thì tôi ít khi gặp phải, nhưng tôi khá chắc chắn về cái thứ nhất: name variable, name function, name class, name file, name asset ...
Xin chào tất cả mọi người, James Nguyễn quay trở lại với các bạn cùng phần tiếp theo trong series Clean Code mang tên Meaningful Names đây. Cache invalidation thì tôi ít khi gặp phải, nhưng tôi khá chắc chắn về cái thứ nhất: name variable, name function, name class, name file, name asset resource, ... name everything. Chúng ta làm điều đó mỗi ngày, mỗi giờ trong công việc lập trình của mình, nhưng chẳng mấy ai để tâm. Liệu có bao giờ bạn bắt gặp một variable mà đọc tên xong không hiểu là nó để làm gì không. Tôi đã gặp nhiều, ... rất nhiều là đằng khác. Đa phần các lập trình viên vẫn coi variable chỉ là một cái gì đó để lưu trữ biến mà không hề nghĩ tới tác dụng to lớn khác của nó. Hãy coi các variable như các key word của một đoạn văn vậy, bạn càng chắt lọc, cẩn thận, kĩ càng để chọn những key word thì đoạn văn đó sẽ càng có ý nghĩa, và tất nhiên, sẽ hay hơn rất nhiều. Tiếp sau đây, chúng ta sẽ đi qua 1 số các rules, các quy tắc mà bạn nên và phải tuân thủ để đảm bảo bạn đang làm tốt cái việc khó khăn nhất nhì trong việc lập trình: Naming.
Use Intention-Revealing Names (Đặt tên có ý nghĩa)
Như đã đề cập đến ở trên, mỗi cái tên bạn đặt cho class, function, variable cũng như các bạn đang đi tìm các key word cho bài văn của mình, đó chính là ý nghĩa của bài văn đó, vì vậy nó cần mang một nội dung nào đó. Hạn chế tối đa việc đặt tên các biến như:
int a, b, c;
Hãy đặt những tên có ý nghĩa:
protected $firstDateInMonth; protected $lastDateInMonth; protected $dateList;
Hãy cùng đi vào ví dụ sau: Ở đây, xin hãy chỉ chú ý vào biến $count mà thôi. Nếu tác giả sử dụng biến $count này như 1 temp variable thì có lẽ cũng chẳng nhắc đến làm gì. Nhưng nó lại là giá trị trả về của function, tức là nó khá quan trọng đấy chứ. Vậy thì tại sao:
- Sao lại là $count, $count ở đây là động từ (tôi đoán thế). Tốt nhất là hãy sử dụng danh từ cho variable, vậy thì $counter mới đúng chứ?
- Bản thân $count hay $counter có mang lại cho bạn chút thông tin gì không ? Nếu chỉ đọc phần comment, tên function và return $count; thì bạn có biết function này trả về gì không?
Đa phần chúng ta dùng $counter khi cần phải đếm trong một vòng lặp nào đó chứ bản thân nó không mang ý nghĩa nào cụ thể. Và theo như tôi đọc code, biến này để lưu số lượng các bản ghi đã được lưu vào database. Vậy ta hoàn toàn có thể đặt tên biến là $insertedReportsCounter, $savedReportsCounter, $savedReportsNumber. Lúc đó người đọc có thể dễ dàng hiểu rằng function này trả về số lượng bản ghi đã được lưu vào database (y).
Nên nhớ rằng không phải có mỗi compiler đọc tên biến của bạn, mà còn có người khác đọc và cảm nhận ý muốn của bạn là gì. Vậy nên đừng tự biến mình thành 1 coder khô khan, đừng chỉ code cho máy đọc, hãy tự biến mình thành một nghệ sĩ, và tạo nên những bài văn (code) dễ hiểu và truyền cảm nhất có thể.
Avoid Disinformation (Tránh sai lệch ý nghĩa)
Không đặt tên thì thôi, đã đặt thì phải chuẩn. Điều này cũng giống như không làm thì thôi chứ đừng phá, hay nhiệt tình + ngu ngốc = phá hoại. Hãy cùng đi vào ví dụ sau đây: Bản thân tôi nghĩ đa phần các lập trình viên, đặc biệt là lập trình viên web php, laravel sẽ hiểu PER_PAGE ở đây ám chỉ số items mỗi page, và bản thân pagination của laravel cũng sử dụng property là per_page
{ "total": 50, "per_page": 15, "current_page": 1, "last_page": 4, "next_page_url": "http://laravel.app?page=2", "prev_page_url": null, "from": 1, "to": 15, "data":[ { // Result Object }, { // Result Object } ] }
Nhưng theo ý kiến của bản thân tôi, sử dụng items_per_page hoặc items_number_per_page hoặc items_quantity_per_page sẽ clear hơn khá nhiều cho người đọc code, để họ hiểu PER_PAGE ở đây nghĩa là số items sẽ list ra trong page, chứ không phải là một cái gì khác.
Ngoài ra, lỗi thứ 2 tôi muốn nhắc tới ở đây. Hãy nhìn biến list($from, $to) = $this->getTimeConstraint($request);. Tôi không có thành kiến gì về việc sử dụng giới từ như from, to làm variable, và tôi nghĩ người đọc cũng sẽ hiểu phần nào mục đích của dòng code này là truyền 2 mốc thời gian vào 2 biến $from và $to, và dự đoán là sẽ get list activities (do tôi thấy tên class là Activities và tên function là index) trong khoảng thời gian này. Nhưng ở đây tôi đặt ra 1 vài câu hỏi:
- $from và $to ở đây là from - to với hour, date, month hay year? (Sở dĩ tôi biết đây là thời gian vì method bên phải getTimeConstraint)
- getTimeConstraint constraint ở đây tức là bắt buộc phải có 2 mốc thời gian để get list. Nhưng tôi không hề thấy bất cứ phần nào, layer nào check nếu không có $from hoặc $to thì sẽ trả về lỗi, mà thậm chí trong đoạn code phía dưới, tác giả còn kiểm tra nếu thiếu 1 trong 2 hoặc cả 2 thì sẽ trả về 1 giá trị mặc định. Vậy tại sao lại đặt là getTimeConstraint ?
Ta sẽ sửa một chút đoạn code này thành như sau:
list($startedDate, $endedDate) = $this->getTimeRange($request);
Bạn nghĩ sao?
Make Meaningful Distinctions (Tạo sự khác biệt về ý nghĩa)
Thực chất Avoid Disinformation và Make meaningful Distinctions khá là giống nhau, mục tiêu vẫn là phải tạo ra được các tên phải nói lên intent, nội dung của nó. Ở đây, Make meaningful Distinctions hướng đến việc các names mà chúng ta đặt ra sẽ phải thực sự khác nhau. Cứ thi thoảng tôi lại gặp các variable kiểu kiểu như $teacher, $teacherInfo, $teacherData, ... Rốt cuộc thì lesson và lessonData khác nhau chỗ nào mà lại đặt là lessonData
Hoặc 1 ví dụ mà tác giả đưa ra:
public static void copyChars(char a1[], char a2[]) { for (int i = 0; i< a1.length; i++) { a2[i] = a1[i] } }
Đoạn code này sẽ rõ ràng hơn nhiều nếu thay a1 bằng source và a2 bằng destination
Use Searchable Names (Tên có thể tìm kiếm được)
Các biến mà chỉ có một kí tự, hoặc là một số nào đó sẽ không mang một ý nghĩa nào cả, thêm nữa, ta sẽ khó khăn tìm kiếm giá trị đó ở trong project. Đọc đoạn code sau, bạn có hiểu 270, 445, 245, hay 5 là gì không. Thú thực là đến chính tôi là 1 thành viên trong team mà còn chẳng hiểu thì các bạn làm sao mà hiểu được. Và cứ mỗi lần tôi làm gì động tới function này, cũng mất khoảng 30' để hoặc đi hỏi xem mấy cái này để làm gì, hoặc test / predict xem rốt cuộc nó có tác dụng gì. Thời điểm mà tôi viết bài này là 16:45, chủ nhật 23/07/2017. Các thành viên trong team hiện không có một ai nên tôi cũng không thể refactor lại code cho dễ hiểu hơn được (ngoài ra thì tôi chắc đến 80% là người code nên function này cũng chẳng còn có thể nhớ được mấy số này mang ý nghĩa gì nữa), vậy nên mạn phép lấy ví dụ mà tác giả Robert Martin đưa ra để cùng thảo luận
for (int j=0; j<34; j++) { s += (t[j] * 4) / 5; }
Refactor code và đem cho các variables những cái tên thật kêu:
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_WEEEK); sum += realTaskWeeks; }
Pick One Word per Concept (Sử dụng đồng nhất)
Hãy chọn ra 1 từ cho 1 khái niệm nhất định, và tuân theo nó xuyên suốt cả project. Ví dụ trong lập trình web, chúng ta xây dựng các API theo chuẩn RESTful (sử dụng các HTTP methods CRUD), hãy xây dựng các controller dựa trên việc đó. Trong laravel có hỗ trợ việc xây dựng một resource controller đáp ứng đầu đủ các CRUD routes. Bạn chỉ việc chạy lệnh sau:
php artisan make:controller NameOfController --resource
Khi đó laravel sẽ sinh ra các method như index, create, store, show, ... Từ sau đó, hãy chỉ sử dụng các method này, điều này sẽ hạn chế việc bạn confused khi phải chọn giữa store/add/insert, fetch/retrieve/get, ... Đồng thời, tuân theo một chuẩn, một key word xuyên suốt cũng sẻ giúp giảm tải công việc cho các lập trình viên khác. Ví dụ tôi biết là có controller PostsController chẳng hạn, và cũng biết là tác giả đã khai báo route để lấy tất cả các post, thì chẳng có lý do gì mà tôi phải đọc code cả, đó chắc chắn phải là method index, vậy nên nếu cần sửa gì, hoặc có lỗi trong việc get tất cả posts thì tôi chỉ việc tìm đến đúng method này thôi.
Nhưng thi thoảng tôi lại thấy các method đại loại thư getTeacher, getAllTeachers, ... Thay vì sử dụng method index như thường lệ, ta lại thấy ở đây là getPostList. Chắc phải có gì đó đặc biệt ở đây, nên tôi quyết định ngồi mày mò thêm chút nữa và khá ngạc nhiên khi tôi tìm thấy một PostsController thứ 2. Hai controller tên giống hệt nhau (shock), một controller thì có method index để list tất cả các post, và controller còn lại thì có method getPostList để ... list tất cả các post. (Do là private project nên không thể show hết lên cho mọi người xem, rất tiếc (khoc)) Tên method thì vi phạm nguyên tắc Pick One Word per Concept, tên class thì vi phạm nguyên tắc Make Meaningful Distinctions (facepalm). Tôi cũng không biết phải nói gì nữa.
Others
Còn rất nhiều nguyên tắc khác mà Robert Martin nhắc tới trong quyển sách của mình, tôi sẽ chỉ tóm tắt ra đây bởi những nguyên tắc mà tôi thấy là quan trọng nhất thì đều đã được nhắc tới ở trên rồi.
- Class Names: Hãy sử dụng danh từ hoặc cụm danh từ khi đặt tên class, variable.
- Method Names: Hãy sử dụng đồng từ khi đặt tên method. Accesstors, mutators, predicates nên bắt đầu với get, set, is. Ví dụ như: getName, setAddress, isString.
- Use Solution Domain Names: Đa phần người đọc code của bạn là các programmer, vì vậy họ hoàn toàn có thể hiểu và tuân theo các khái niệm về khoa học máy tính, giải thuật, design pattern, math, algebra, ... Ví dụ với Repository Design pattern trong *Laravel * chẳng hạn, chúng ta sẽ đặt hậu tổ Repository vào sau các class để đánh dấu class đó thuộc layer Repository.
- Use Problem Domain Names: sử dụng các tên liên quan tới project 5.. Avoid Encodings: tránh mã hóa
- Avoid Mental Mapping
- Don’t Add Gratuitous Context
- Final Word
- ....
Conclusion
Tôi vẫn nhớ cái hồi tôi áp dụng phần này của quyển sách vào cách mình code, tôi ngồi gần 1 tiếng đồng hồ chỉ để tìm cách đặt 1 cái tên biến. Và các bạn biết gì không, tôi quyết định bỏ cuộc, nghĩ 1 cái tên hoàn hảo tuân thủ gần hai chục nguyên tắc là điều quá khó với thằng coder như tôi. Tôi đặt bừa 1 cái tên, cắm đầu cắm cổ vào code. Rồi đến lúc chuẩn bị gửi pull request, tôi soát lại 1 lần nữa, nghĩ xem nên đặt tên gì, rồi thay đổi lại cho hợp lí. Các bạn không nhất thiết phải tuân thủ tuyệt đối toàn bộ các nguyên tắc tôi đã nếu. Hoàn thiện hơn hoàn hảo. Hãy cứ code đã, sau đó chúng ta sẽ chỉnh sửa lại sau. Nhưng không vì thế mà tương bừa 1 cái tên cho variable hay class. Hãy dành khoảng 2 phút cho việc đặt tên bất cứ cái gì đó. Ban đầu sẽ tốn thời gian, nhưng sau rồi bạn cũng sẽ quen dần mà thôi. Vậy là đã đến phần cuối của bài viết. Bạn cảm thấy thế nào? Quá nhiều nguyên tắc phải tuân theo chỉ để làm 1 việc quá đổi đơn giản và quen thuộc: Naming. Vấn đề đặt tên này khó hơn rất nhiều so với tưởng tượng. Không nên phức tạp hóa việc đặt tên biến. ... Bất cứ câu hỏi, cảm nhận, quan điểm gì về bài viết, hãy comment ngay phía dưới để tôi có thể biết được. Tôi không dám chắc 100% những gì tôi viết là đúng, vậy nên rất mong nhật được feedback từ các bạn. Cám ơn đã đi cùng tôi tới tận phần này của bài viết. Hẹn gặp lại vào phần tiếp theo của series Functions