Đặt tên trong clean code (Phần 1)

01/11/2020 - lượt xem
Chia sẻ
 
Rate this post

Chào tất cả các bạn hôm nay chúng ta lại tiếp tục với cuốn sách clean code. Ơ bài trước chúng ta đã nói tổng quan về clean code. Tiếp đến bài hôm nay ta sẽ nói đến đặt tên trong cuốn sách.

Tên ở khắp mọi nơi trong phần mềm. Chúng ta đặt tên cho các biến, hàm, đối số, lớp và package. Chúng ta đặt tên cho các file , các folder. Tên có ở mọi nơi . Bởi vì chúng ta cần đặt tên rất nhiều, nên chúng ta cần làm tốt điều đó. Cần tuân theo một số quy tắt để tạo được một cái tên tốt dễ nhớ, dễ hình dung ra ý nghĩa tồn tại của biến, hàm, đối số hay lớp đó….

1. Đặt tên theo mục đích sử dụng

Việc đặt tên khá quan trọng, nó có thể giúp cho các đồng đội cùng team dễ dàng hiểu đoạn code bạn đang viết hơn. Đương nhiên không dễ dàng để chọn được 1 cái tên tốt, nhưng chắc chắn thời gian bỏ ra lựa chọn tên tốt là vô cùng xứng đáng.

Tên biến, hàm, class… tất cả nên trả lời cho 1 câu hỏi lớn. Đó là tại sao nó tồn tại, nó là cái gì, và sử dụng nó như thế nào. 

Ví dụ 1:

int d; // elapsed time in days

Cách đặt tên bên trên không thực sự tốt. Tên biến d không làm chúng ta liên tưởng gì đến thời gian cả. Có 1 vài gợi ý khác việc này.

int elapsedTimeInDays; 

int daysSinceCreation; 

int daysSinceModification; 

int fileAgeInDays;

Ví dụ 2.

code:

public List<int[]> getThem() {

List<int[]> list1 = new ArrayList<int[]>();

for (int[] x : theList)

if (x[0] == 4)

list1.add(x);

return list1;

}

Phân tích code:

Theo bạn thì đoạn mã trên xử lý vấn đề gì? Không có biểu thức nào là phức tạp, khoảng cách hợp lý, cả đoạn mã chỉ có vài biến mà thôi. Cấu trúc cũng không quá phức tạp với 1 vòng forif.

Thực vậy, đoạn mã này khá tối nghĩa, nó không được đặt trong một ngữ cảnh rõ ràng. Ngoài ra có những câu hỏi khó giải đáp nếu nhìn vào đoạn mã trên như là:

+ Biến theList thực sự là gì?

+ ý nghĩa của x[0] là gì? Tại sao lại là x[0] ?

+ ý nghĩa của giá trị 4 ? Tại sao là 4 mà không phải số khác ?

+ Sử dụng danh sách được trả về như thế nào

Nếu chỉ dựa vào đoạn mã trên, những câu hỏi trên thực khó mà trả lời hết được. Thực tế đây là 1 đoạn mã trong game dò mìn. 

Tối ưu code:

public List<int[]> getFlaggedCells() {

     List<int[]> flaggedCells = new ArrayList<int[]>();

     for (int[] cell : gameBoard)

          if (cell[STATUS_VALUE] == FLAGGED)

          flaggedCells.add(cell);

          return flaggedCells;

}

Biến theList cũ được đổi tên thành gameBoard. Mỗi 1 ô trên bàn cờ được sử dụng như là 1 array. Phần tử thứ 0 của array này là trạng thái của ô đó, nếu trạng thái này bằng 4 thì có ý nghĩa là ‘flagged’

Đoạn mã sau khi chỉnh sửa đã sáng sủa hơn rất nhiều rồi, tuy vậy chúng ta vẫn có thể cải tiến thêm 1 chút nữa.

Mỗi ô trên bàn cờ thay vì biểu diễn dưới dạng array, chúng ta có thể viết về dạng class Cell. Khi đó việc kiểm tra có phải có trạng thái ‘flagged’ hay không thì chỉ cần gọi hàm isFlagged là xong. Việc này làm cho đoạn mã của chúng ta tường minh hơn rất nhiều, tất cả chỉ nhờ việc đặt lại tên một cách phù hợp mà thôi.

public List<Cell> getFlaggedCells() {

       List<Cell> flaggedCells = new ArrayList<Cell>();

       for (Cell cell : gameBoard)

             if (cell.isFlagged())

             flaggedCells.add(cell);

             return flaggedCells;

}

Tránh nhầm lẫn, sai lệch thông tin ( Avoid Disinformation )

Việc đặt tên biến cũng hạn chế sử dụng những từ khóa là tên gọi hoặc các từ trong 1 hệ thống. Viết tắt các từ cũng cần chú ý tránh các từ thông dụng quá. Việc này sẽ giúp cho đoạn mã tránh bị hiểu sai lệch theo một ý nghĩa hoàn toàn khác đi.

Ví dụ như đặt tên biến là ie thì có thể hiểu theo nhiều nghĩa khác là internet explorer hay là i.e (viết tắt của that is)

Cẩn thận khi sử dụng đặt tên cho những biến, class… khác nhau nhưng các tên đó rất khó để phân biệt khác nhau chỗ nào

Ví dụ như:

XYZControllerForEfficientHandlingOfStrings

XYZControllerForEfficientStorageOfStrings

Rất khó để chúng ta phân biệt được sự khác biệt giữa 2 cái tên này.

1 ví dụ đặt tên cho biến kinh điển khác đó là viết dạng thường của L à chữ hoa của chữ O. Lúc này nhìn nó giống như là số 1 và 0 vậy

int a = l;

if ( O == l )

a = O1;

else

l = 01;

Tạo sự khác biệt có ý nghĩa ( Make meaningful Distinctions )

Vấn đề này thường xảy ra khi mà chúng ta viết code với mục đích chỉ để chạy mà thôi. Cho ví dụ khá phổ biến với việc này là đặt tên biến dạng theo số đếm như : a1, a2, a3… an hay thậm chí là tên hàm: doSomething1, soSomething2…

Việc này thường sinh ra do việc các biến, hàm được clone ra với 1 mục đích gần giống như hàm ban đầu, chỉ cso 1 chút thay đổi nhỏ. Và thế là hàng loạt các số thứ tự được thêm vào.

public static void copyChars(char a1[], char a2[]) {

    for (int i = 0; i < a1.length; i++) {

        a2[i] = a1[i];

    }

}

Chỉ cần thay đổi 1 chút a1 và a2 ta sẽ có đoạn mã dễ hiểu hơn rất nhiều

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

Chỉ cần thay đổi nhỏ trong cách đặt tên chúng ta sẽ có những ý nghĩa hoàn toàn khác nhau

getActiveAccount(); // Lấy 1 tài khoản đang active hiện tại ( ở góc độ nào đó có thể hiểu, 
\\chỉ có 1 tài khoản active, đó là tài khoản cần lấy ra, giống như trường hợp đăng nhập)

getActiveAccounts(); // Lấy tất cả các tài khoản đang được active

getActiveAccountInfo(); // Lấy thông tin tài khoản của tài khoản đang active

Sử dụng các tên có thể đọc được ( Use Pronounceable Names )

Thực sự tồi tệ khi mà chúng ta trao đổi với nhau về 1 đoạn mã mà trong đó toàn những tên hàm tên biến không biết phát âm như thế nào. 

Xem ví dụ sau đây:

class DtaRcrd102 {

    private Date genymdhms;

    private Date modymdhms;

    private final String pszqint = "102";

     /* ... */

};

Bạn sẽ đọc tên class DtaRcrd102 hay biến genymdhms như thế nào? Thật là kinh khủng, vì ngay cả việc gõ đúng tên class thôi cũng thấy bực mình rồi

class Customer {

    private Date generationTimestamp;

    private Date modificationTimestamp;;

    private final String recordId = "102";

    /* ... */

};

Và đây là đoạn code sau khi được sửa lại, mọi thứ thật dễ dàng và gần gũi với cuộc sống thường ngày của chúng ta hơn rất nhiều.

Use Searchable Names : Sử dụng tên có thể tìm kiếm được

Tên biến 1 kí tự và hằng số thực sự không thể dễ dàng xác định (tìm kiếm) trong mã nguồn của chúng ta. 

Ví dụ với tên biến là a, e, i… thì rất khó khăn để tìm kiếm. Các từ này đều là nguyên âm và được sử dụng nhiều trong tiếng anh, chính vì thế càng khó khăn hơn khi tìm kiếm. 

Tóm lại, nếu 1 biến hoặc hằng số được sử dụng lại nhiều lần trong code của chúng ta thì nó cần phải đặt tên một cách dễ tìm kiếm

Ví dụ

for (int j=0; j<34; j++) {

    s += (t[j]*4)/5;

}

Sẽ được chuyển thành 

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;

}

Tránh mã hóa code (Avoid Encodings)

Hungarian Notation

Tham khảo thêm về Hungarian Notation tại đây: http://web.mst.edu/~cpp/common/hungarian.html

Trước đây khi viết code, chúng ta thường dùng (hoặc được dạy) rằng nên thêm các viết tắt kiểu dữ liệu trước mỗi tên biến. Ví dụ như là: strName, iCount

Tuy vậy việc này hiện nay cũng không còn nhiều ý nghĩa nữa, khi mà các IDE ngày càng thông minh hơn. Chúng có thể phát hiện ra lỗi về kiểu dữ liệu trước khi thực hiện biên dịch. 

Do đó, việc thêm các tiền tố kiểu dữ liệu này vô hình chung làm code rườm rà và khó đọc hơn khá nhiều.

Tiền tố ( Member Prefixes )

Chúng ta cũng không cần phải đặt tên với tiền tố m_ ở khắp mọi nơi. Các lớp, hàm đủ nhỏ để không phải sử dụng đến các tiền tố đó. Như một lẽ thường thì chúng ta thường đọc mã nhiều hơn là nhìn tiền tố. Vô hình chung tiền tố lại trở nên lộn xộn và vô hình, mất đi ý nghĩa ban đầu của nó.

public class Part {

    private String m_dsc;

    void setName(String name) {

        m_dsc = name;

    }

}

Được đổi thành:

public class Part {

     String description;

     void setDescription(String description) {

          this.description = description;

     }

}

 Interfaces and Implementations

Đôi khi có 1 vài trường hợp đặc biệt cần đế mã hóa này. Ví dụ như khi chúng ta xây dựng 1 Abstract Factory cho việc tạo ra các Shape (hình khối). Factory này là 1 interface, khi đó việc đặt tên cho interface này và class thể hiện của nó cũng là 1 vấn đề cần xem xét.

Chúng ta có thể đặt cho interface với tên : IShapeFactory và class thể hiện là ShapFactory, hoặc là Interface ShapeFactory, class thể hiện là ShpeFactoryImp.

Tránh ánh xạ tinh thần ( Avoid mental mapping )

Thông thường các biến đếm trong vòng lặp thường được đặt là i, j, k (không bao giờ đặt là l vì sự nhầm lẫn) do sử dụng của nó là nhỏ, không bị xung đột. Tuy nhiên trong hầu hết các bối cảnh khác, tên biến dạng 1 chữ cái là 1 sự lựa chọn tồi tệ.

Những lập trình viên thường là những người khá thông minh. Những người thông minh thường muốn thể hiện bằng cách “chơi đùa”, “tung hứng” với đoạn code của mình. Nhưng sau tất cả, trừ khi bạn thực sự rất rất thông minh, còn không bạn sẽ chẳng nhớ rổi những biến như r, a , b đã từng được viết tắt cho biến có ý nghĩa gì.

Sự khác biệt giữ lập trình viên thông mình và lập trình viên chuyên nghiệp đó là những người chuyên nghiệp hiểu rằng “Sự rõ ràng chính là vua”. Những lập trình viên chuyên nghiệp viết code để người khác có thể hiểu được một cách dễ dàng nhất.

Phần 1 sẽ dừng tại đây thôi nhé anh em. Qua bài bài viết này anh em hãy đặt tên biến sao cho hiểu quả, rõ ràng. nên nhớ “Sự rõ ràng chính là vua“.

Sang bài tiếp theo là “Đặt tên trong clean code (Phần 2)” chúng ta sẽ tìm hiểu về thêm về Đặt tên class, method, function … hãy cùng tìm hiểu nhé anh em.

Bài viết liên quan:

    Liên hệ với chúng tôi

    Để lại thông tin để nhận được các bài viết khác

    Rate this post

    Xem thêm nhiều bài tin mới nhất về Clean code

    Xem thêm