+3

Clean code - P1

Clean code, trước đây được thầy giáo giới thiệu đọc cuốn này, cơ mà đó là hồi còn sinh viên, chưa chú tâm và cũng chưa thật hứng thú lắm với những đầu sách như vậy. Gần đây được anh Ngọc leader team mình giới thiệu cho một cuốn sách,... vẫn là Clean Code.

Không còn nghi ngờ gì nữa, ngay tối hôm đó mình tải về và đọc luôn. Vài hôm sau khi đọc hết chương 2, chương 3, nghĩ lại thấy những dòng code của mình nhiều khi vẫn còn bẩn bựa. Dự án hiện tại mình đang đọc cũng là source code từ năm 2004-2009, những class vài nghìn dòng, những function vài trăm dòng, tên biến thì của chuối,..., nghiệp vụ thì loằng ngoằng. 😦 , quả là địa ngục !!!!

Nay ngồi viết lại bài này, mình muốn chia sẻ những gì mình học được trong cuốn sách. Mình cũng mới đọc thôi và dĩ nhiên đọc một lần thì chưa thể thấm hết ngay được, mình sẽ cố gắng chia sẻ một cách ngắn gọn, cô đọng nhất.

Chương 1 - Clean Code

There Will Be Code

Những dòng code sẽ là cái chúng ta thể hiện yêu cầu, nghiệp vụ, spec của khách hàng, và điều này không thể khác được. Dù công nghệ có thay đổi, có tiên tiến đến đâu thì việc viết code sẽ gắn với lập trình viên đến cho đến khi chúng ta bỏ nghề. Đại loại là code, code nữa, code mãi!!! Vì vậy hãy cố gắng cải thiện kỹ năng viết code.

Bad Code

Đoạn này trong sách mình ấn tượng với luật của LeBlanc: Later equals never (Để sau có nghĩa là không bao giờ).

Thi thoảng vi deadline, sếp thúc đít, chúng ta muốn làm nhanh, vội vàng ... cố code cho xong miễn là chạy được và tự nhủ sẽ quay lại dọn dẹp sau. Nhưng một sự thật khó chấp nhận là để sau nghĩa là không bao giờ.

The Total Cost of Owning a Mess

Sách không nói rõ, nhưng đoạn này có vẻ là nói đến các dự án mới khi mà team dự án bắt đầu những dòng code đầu tiên.

Bắt đầu dự án thì team thực hiện rất nhanh. Nhưng dần về sau, spec thay đổi, những lập trình viên phá vỡ dần cấu trúc code, phá vỡ một vài function. Code dần trở lên phức tạp, hệ thống bắt đầu rối rắm,... và những vấn đề đó lớn dần theo thời gian mà không có cách gì clean nữa được.

Dự án bắt đầu chậm lại, trong khi Sếp thì cứ thúc nhanh và muốn thêm người. Một vấn đề lớn nữa lại phát sinh khi thêm nhân sự vào dự án đang chậm (cái này là hồi sinh viên thầy giáo dạy) : Dự án chậm -> thêm nhân sự chống cháy -> nhân sự mới không hiểu hệ thống -> người cũ mất thời gian giải thích hệ thống -> người mới làm mà chưa thực sự hiểu -> tiêu diêu -> áp lực -> tiêu diêu -> áp lực -> cháy to hơn.

Attitude

Có một ví dụ khá hay thế này.

Ông bác sĩ không rửa tay làm bệnh nhân nhiễm khuẩn mà chết rồi ra trước tòa cãi lại rằng: thằng cha bệnh nhân yêu cầu tôi mổ luôn cho nhanh, không cần phải rửa tay, tui có ghi âm lại đàng hoàng nè. =))

Nhiều khi manager cũng như tay bệnh nhân kia, chả biết quái gì về Code sạch (và cái giá phải trả cho hàng loạt Code bẩn). Vậy nên nghĩa vụ của một lập trình viên chuyên nghiệp là bảo vệ Code của mình (mặc dù chậm Deadline nhưng vẫn phải sạch).

Cái này thì mình vẫn đặt một dấu hỏi?

Update: Và sau nhiều năm đi làm, đã từng làm cả member và team lead thì việc chậm deadline là một việc khó chấp nhận hơn việc cố gắng Clean code. Hãy cố gắng sắp xếp clean code giai đoạn sau đó nếu chúng ta là những dev có tâm.

The Boy Scout Rule

"Rời khỏi khu cắm trại sạch hơn khi chúng ta đến!"

Mình hiểu đơn giản là đừng làm mọi thứ rối rắm hơn tình trạng trước đó.

Conclusion

Đọc một cuốn sách mà giúp ta tốt lên ngay được thì chắc ai cũng sẵn sàng đọc ngay. Cuốn sách này cũng vậy, nhóm tác giả không hứa sẽ giúp chúng ta trở lên giỏi hơn ngay tức thì nhưng nó có thể giúp ta từ từ từng bước, thấm dần rồi chúng ta sẽ nhận thấy sự đổi mình để viết code tốt hơn, trở thành một lập trình viên chuyên nghiệp.

Chương 2 - Meaningful Names

Use Intention-Revealing Names

Cần chọn tên biến, tên function hợp lý, cái tên thể hiện ý nghĩa tường mình, nó dùng để làm gì để người đọc source code dễ hiểu, và ngay cả chúng ta khoảng một tháng sau đọc lại vẫn có thể hiểu ngay được.

// Cách 1
public List<int[]> getThem() {
   List<int[]> list1 = new ArrayList<int[]>();
      for (int[] x : theList)
         if (x[0] == 4)
            list1.add(x);
   return list1;
}

// Cách 2
public List<int[]> getFlaggedCells() {
   List<int[]> flaggedCells = new ArrayList<int[]>();
      for (int[] cell : gameBoard)
         if (cell[STATUS_VALUE] == FLAGGED)
            flaggedCells.add(cell);
   return flaggedCells;
}

// Cách 3
public List<Cell> getFlaggedCells() {
   List<Cell> flaggedCells = new ArrayList<Cell>();
      for (Cell cell : gameBoard)
         if (cell.isFlagged())
            flaggedCells.add(cell);
   return flaggedCells;
}

Rõ ràng nhìn vào cách viết 3 tốt hơn nhiều so với cách viết 1 và 2. Một ví dụ khác,

public static void copyChars(char a1[], char a2[]) {
   for (int i = 0; i < a1.length; i++) {
      a2[i] = a1[i];
   }
}

Hai biến a1[], a2[] thay bằng source[] và destination[] sẽ tường minh hơn.

Make Meaningful Distinctions

Hãy cẩn thận trong việc sử dụng các tên gọi nhiều nghĩa họặc nghĩa giống nhau. Sự riêng biệt giữa các tên gọi cần phân biệt rõ ràng.

Ví dụ tên một biến không bao gồm 'variable', tên một bảng không bao gồm 'table', vì nó dư thừa.

Ví dụ khác, ProductInfo và ProductData. Để map thông tin của một sản phẩm với một class thì việc đặt tên là ProductInfo tường minh hơn, dù chúng ta thấy Info và Data cũng same same về nghĩa.

Use Pronounceable Names

Thông tin cần tường minh để dễ nhớ, dễ hiểu. Cùng theo dõi 2 cách viết dưới đây, khi class ở cách viết 1 mà được sử dụng ở một chỗ khác thì việc phải nhớ tên biến kiểu như genymdhms, modymdhms quả là RIP.

// Cách 1
class DtaRcrd102 {
   private Date genymdhms;
   private Date modymdhms;
   private final String pszqint = "102";
   /* ... */
};

// Cách 2
class Customer {
   private Date generationTimestamp;
   private Date modificationTimestamp;;
   private final String recordId = "102";
   /* ... */
};

Use Searchable Names

Một số biến và hằng được sử dụng nhiều nơi trong class thì hãy đặt những biến đó thành biến chung, có thể đặt sang các class util, common, hoặc lên đầu mỗi class đó (nếu chỉ sử dụng trong một class). Đoạn code dưới đây, cách 2 được cải thiện hơn cách 1, rõ ràng và dễ hiểu mục đích hơn.

// Cách 1
for (int j=0; j<34; j++) {
   s += (t[j]*4)/5;
}

// Cách 2
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;
}

Class Names

Tên Classes và Objects nên là danh từ hoặc cụm danh từ. Tránh những từ như Manager, Processor, Data, or Info làm tên một class. Tên class cũng tránh là một động từ.

Method Names

Method gắn với chức năng, hành động của object class. Vì vậy nó nên được đặt tên là các cụm động từ, động từ. Các phương thức dùng để truy xuất, update thông tin nên gắn thêm các tiền tố get/set, is. Ví dụ,

string name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted())...

Cuốn sách cũng đề cập đến vấn đề tạo một đối tượng bằng static factory method tốt hơn là dùng cách new Object với nhiều Contructor overloads.

Complex fulcrumPoint = Complex.FromRealNumber(23.0);

tốt hơn là:

Complex fulcrumPoint = new Complex(23.0);

Don't Be Cute

Đừng cố tỏ ra thông minh với những tên biến hóm hỉnh, ẩn dụ mà người đọc chẳng thế nào hiểu được hoặc khó để luận ra ý nghĩa của nó.

Chung quy lại là hãy nói những gì chúng ta thấy có ý nghĩa.

Pick One Word per Concept

Fetch, retrieve, và get. 3 từ này cũng same same về nghĩa. Thật là khó để nhớ mục đích chúng làm cái gì nếu đặt tên như vậy. Nên hãy chọn một từ cho một khái niệm trừu tượng và gắn nó vào với khái niệm đó, chúng ta sẽ mất ít thời gian hơn để có thể hiểu nó.

Ví dụ, controller, managerdriver cũng same về nghĩa, nhưng từ controller được chọn để gắn với khái niệm điều hướng xử lý trong mô hình MVC, trong các framework.

Use Solution Domain Names, Use Problem Domain Names

Người đọc code của chúng ta sẽ là các lập trình viên khác, vì vậy hãy chọn những từ ngữ liên quan đến toán học, thuật toán, khoa học máy tính, ... hoặc những từ ngữ gắn với nghiệp vụ. Để ít nhất người maintain có thể hiểu được nghiệp vụ thông qua ý nghĩa của nó. Chức năng này thực hiện nghiệp vụ gì, chức năng này giải quyết vấn đề gì,...

Add Meaningful Context

Hãy tưởng tượng chúng ta có các biến với tên như sau: firstName, lastName, street, houseNumber, city, state và zipcode. Đặt chúng cạnh nhau thì rất rõ là sẽ tạo thành một địa chỉ. Nhưng biến state có nghĩa gì nếu chúng ta thấy nó một mình trong một phương thức? Liệu chúng ta có hiểu ngay đó là một phần của một địa chỉ?

Giải pháp kinh điển và có lẽ là tốt nhất trong trường hợp này là tạo ra một context chứa thông tin trên, đặt tên class là Address. Hoặc ít nhất có thể sửa tên biến thành addrFirstName, addrLastName, adddrState,... Ít nhất người đọc sẽ hiểu rằng những biến này là phần của một cấu trúc lớn hơn.

Cũng cần lưu ý rằng tên ngắn tốt hơn là tên dài, liệu thêm tiền tố hậu tố vào tên biến, tên hàm là có cần thiết không.

Conclusion

Tổng kết chương 2, ngắn gọn là "cái tên nói lên tất cả". Hãy chọn tên biến, tên hàm hợp lý để người đọc code có thể dễ hiểu nhất có thể.

Mình sẽ tách 2 chương trong cuốn sách thành một bài viết, tùy thuộc vào nội dung của chương đó có dài không. Bài hôm nay đến đây thôi, mình sẽ dịch loạt bài sau sớm. Hi vọng loạt bài này hữu ích với mọi người!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí