Viết code thế nào cho sạch?

 

Viết code thế nào cho sạch?

Thời xưa, con người ta thường chú trọng đến hiệu suất chương trình, nên các tên gọi của các biến, các phương thức thường được đặt một cách khá sơ sài. Càng về sau, người ta càng nhận ra được giá trị của việc có một đoạn code sạch – đoạn code được ghi một cách rõ ràng, dễ hiểu – là như thế nào.




Mình khuyên bạn nên đọc thử cuốn sách Clean Code để có cái nhìn chi tiết hơn. Bài viết này mình chỉ dựa vào cuốn sách đó để tổng hợp một số ý chính.

Nếu bạn có một mớ code “bẩn” – đoạn code không được trau chuốt về câu từ, các tên biến được đặt lung tung, thì theo thời gian, nó sẽ như thế này.



Bạn thường được gọi là “author” (tác giả) của một chương trình mà bạn viết, vì chương trình của bạn sẽ viết cho những người khác đọc. Và thậm chí, kể cả bạn đọc code cũng nhiều hơn cả viết code đấy nhé! Bạn có tin không?

Vào những năm 80, chúng ta có một trình biên dịch tên là Emacs. Đặc điểm của chương trình này là ghi nhận lại mọi hành động của bạn.  Tác giả của cuốn sách, Robert C.Martin – hay còn gọi là Uncle Bob, đã thử làm việc trong hàng giờ và xem lại hoạt động của mình. Và, thật ngạc nhiên, hầu hết thời gian của ông chỉ để dành cho việc lăn chuột và xem các module khác!

Bob truy cập vào một module

Ông lăn xuống tìm một phương thức cần chỉnh sửa

Ông dừng lại, suy nghĩ

Ôi, ông ta lại lăn lên đầu để xem xét khai báo biến

Giờ thì ông ta quay lại chỗ cũ và nhập gì đó

Ooops, ông ta xóa đoạn mã ổng vừa nhập rồi

Ông ta lại nhập

Lại xóa nữa

Ông ta nhập tiếp một cái gì đó nhưng bất chợt lại xóa

Ông ta đi tìm phương thức khác, cái mà gọi phương thức ông đang chỉnh sửa, để xem cách nó gọi như thế nào!

Ông ta quay lại và gõ y như lúc nãy vừa mới xóa

Ông ta dừng lại

Ông ta lại xóa đoạn mã đó nữa

Ông ta nhìn vào một cửa sổ khác và xem các subclass. Liệu rằng hàm này có overridden không?

Bạn có thể thấy rằng khoảng hơn 90% anh ta dành ra chỉ để đọc đi đọc lại đoạn code cũ. Chúng ta hầu như đọc lại đoạn code cũ nhiều hơn là viết một đoạn code mới. Vì việc đọc chiếm rất nhiều thời gian, nên việc chúng ta làm đoạn code dễ đọc hơn cũng là cách giúp chúng ta dễ viết hơn.

Mình nghĩ rằng một đoạn mở đầu ngắn thế này cũng đủ để cho các bạn hiểu tầm quan trọng của code sạch là gì rồi nhỉ. Giờ chúng ta sẽ tìm hiểu những quy tắc đặt tên chương trình cho dễ hiểu nhé.

Lưu ý là các quy tắc ở đây có thể bạn cảm thấy đúng, có thể không. Quan trọng là bản thân bạn, và những người xung quanh bạn có thể đọc và hiểu được đoạn code của bạn một cách dễ dàng. Vậy nên là, đừng cứng nhắc quá nhé!

1.    Đặt tên phải thể hiện rõ ý định của bạn

Khi mới bắt đầu học và học về thuật toán, bạn sẽ dễ bắt gặp các cách đặt tên biến sử dụng các kí tự trong bảng chữ cái như a, b, c, d,… Tuy nhiên, đó không phải là một cách hay, vì nó không thể hiện rõ được ý nghĩa của biến, của hàm, của đối tượng mà mình đặt tên đó. Ví dụ, mình có một chương trình:

public List<Integer> abc(List<Integer> a){

   List<Integer> b = new ArrayList<Integer>();

   for(int x: a)

    if(x == 0 || x == 1){

             b.add(x);

    }

    }

    return b;

}

Khi bạn đọc vào, bạn có hiểu chương trình thực sự có ý nghĩa gì không? Đọc đoạn code trên, bạn có cảm thấy thắc mắc rằng:

-        Hàm abc có ý nghĩa gì?

-        Danh sách a chứa những gì?

-        Tại sao lại kiểm tra x = 0 hay x = 1? Hai con số đó có mục đích gì?

-        Danh sách b là cái gì? Tại sao lại trả về danh sách b?

Ok, vậy, mình sẽ nói cho mọi người biết bài toán gì. Đây là bài toán tìm phòng trống để đặt phòng. Danh sách được truyền vào chính là danh sách các phòng với ba trạng thái: 0 – Không có ai ở cả, 1 – Có người ở nhưng có thể ở chung được, 2 – Có người ở nhưng không thể ở chung. Một vị khách muốn thuê phòng và anh nhân viên phải thực hiện phương thức này để xác định xem những phòng nào anh ta có thể ở được. Ở đây chúng ta sẽ đặt tên các biến và phương thức lại để nó dễ đọc hơn.

public List<Integer> getAccommodableRooms(List<Integer> rooms){

   List<Integer> accommodableRooms = new ArrayList<Integer>();

   for(int room: rooms)

    if(room == EMPTY_ROOM || room == SHARED_ROOM){

             accommodableRooms.add(room);

    }

    }

    return accommodableRooms;

}

Chúng ta đã đã thay đổi các con số bằng một hằng có khai báo, để tên của nó được thể hiện rõ nét hơn

Bằng cách sử dụng những từ ngữ có thể hiện rõ mục đích, chúng ta đã giúp cho code dễ đọc hơn. Tuy nhiên, cấu trúc của code vẫn còn chưa được thay đổi, và nó còn khá sơ sài. Chúng ta sẽ chỉnh sửa lại như sau.

public List<Room> getAccommodableRooms(List<Room> rooms){

   List<Room> accommodableRooms new ArrayList<Room>();

   for(Room room: rooms)

    if(room.isRentable()){

             accommodableRooms.add(room);

    }

    }

    return accommodableRooms;

}

Chúng ta đã tạo một đối tượng để thể hiện rõ giá trị trả về là một cái “Room” thay vì là một con số nguyên “integer” (Mặc dù room định nghĩa cơ bản chỉ là mã số phòng). Tiếp đó, chúng ta dùng hàm room.isRentable() thay vì thể hiện ra các con số, để thể hiện rõ mục đích của chúng ta chỉ là kiểm tra liệu phòng đó có thể thuê được hay không, thay vì phải thể hiện ra bằng các con số.

Tuy nhiên, nếu như lạm dụng quá mức sự gói gọn các đoạn code vào trong một phương thức sẽ làm khó khăn trong việc nhìn thấy được chi tiết của chương trình. Đặc biệt là khi các bạn làm thuật toán, bạn thường sẽ cố gắng viết để bạn có thể hiểu càng chi tiết càng tốt. Bạn sẽ cảm thấy rất khó khăn nếu như bạn lướt qua một giải thuật mà phải mở ra một đống phương thức để xem bên trong nó viết cái gì (mình đã từng bị vậy). Vậy nên, tóm gọn lại, tùy vào độ chi tiết bạn muốn người khác nhìn thấy mà bạn sẽ gói gọn nó theo cách phù hợp với mình.

2.    Tránh việc sai lệch thông tin

Okay, giờ bạn đã biết rằng không được đặt cái tên một cách vô nghĩa, cần phải đặt sao cho nó có thông tin, có ý nghĩa. Thế nhưng, bạn có bao giờ nghĩ rằng, tên bạn đặt thật sự đúng với ý định của bạn không? Có bao giờ nó rất dễ để bị hiểu sai ý nghĩa không?

Bạn có hay đặt tên một danh sách các tài khoản là accountList, danh sách các học sinh là studentList,… không? Nghe giống tả mình quá nhỉ haha! Nếu bạn đã từng, thì mình nghĩ bạn nên tập thay đổi cách gọi khác, vì trong Java thì List còn là một kiểu dữ liệu, nếu bạn không khai báo danh sách là một List mà đặt tên lại sử dụng accountList, studentList thì sẽ dễ gây nhầm lẫn lắm đấy. Thay vì thế, bạn có thể đặt tên gọi là bunchOfAccount hay đơn giản chỉ là accounts thôi.

Cẩn thận trong việc đặt tên quá dài. Bạn nghĩ sao khi đọc một tên biến ghi là XYZUsingForCountNumberOfRooms. Rồi nào thì XYZUsingForTotalPeopleOfRooms. Hai đứa này nhìn nó có vẻ giống nhau, và khi đọc mình sẽ phải dừng một lúc để hiểu ý nghĩa của nó đấy.

Tạo tính nhất quán khi đặt tên cũng khá là quan trọng. Nếu bạn học Java Swing, bạn sẽ để ý rằng các Label thường sẽ có hậu tố chung là Lbl như titleLbl, nameLbl,… Bạn cũng có thể thấy các hàm lấy giá trị sẽ có từ get, thay đổi giá trị sẽ có từ set, và các hàm trả về boolean sẽ có từ is như isAccessable, getName, setName.

Vẫn còn có nhiều quy tắc đặt tên nữa, mà mình sẽ có thể viết thêm một bài khác (hoặc là không vì nó khá dài). Mình vẫn khuyến khích các bạn nếu cảm thấy hứng thú, hãy đọc thử cuốn Clean Code để học hỏi kinh nghiệm và nâng cao khả năng viết code gọn gàng của mình.

Lưu ý rằng bạn phân biệt được code bẩn code sạch không đồng nghĩa với việc bạn biết cách viết code sạch. Cũng giống như việc bạn biết tranh nào đẹp không đồng nghĩa với việc bạn biết vẽ tranh tốt. Cần phải có thời gian luyện tập, kết hợp với việc học hỏi từ những người xung quanh, sẽ giúp cho cách viết code của bạn trở nên tốt hơn.

Nội dung bài viết thuộc về Lê Công Diễn. Mang đi nhớ dẫn nguồn.

 

Người viết: Lê Công Diễn

Mang đi nhớ ghi nguồn

 

Nhận xét

Bài đăng phổ biến từ blog này

Deploy project Springboot MIỄN PHÍ sử dụng Render

Ứng dụng Mã hóa bất đối xứng (Asymmetric cryptography) vào Chữ ký số (Digital Signature)

API và HTTP - Một số khái niệm cơ bản cần biết về Web (Phần 2)