Clean code là vấn đề muôn thuở mà bất cứ lập trình viên nào cũng nên quan tâm.
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” – Martin Fowler
Một lập trình viên giỏi nên biết cách làm cho code của mình dễ đọc, hoặc ít nhất là để nó bớt đỡ mùi đi khi người khác nhảy vào maintain mã nguồn của họ, đôi khi là chính họ, lẩm bẩm chửi bản thân vì viết code bẩn.
Để bắt đầu với chuỗi blog về clean code, mình sẽ lấy tiêu đề bài viết một cách đỡ khô khan hơn các quy tắc thông thường. Bloaters (tạm dịch là cá khô) dùng để ám chỉ những dòng code, những phương thức hay class có độ phức tạp quá lớn khiến ta không thể làm việc thêm nữa. Thường thì chúng không xuất hiện ngay, nhưng nó sẽ “âm thầm” tích luỹ theo thời gian. Khi đủ về lượng, chất sẽ đổi, và cơn ác mộng của coder đã đến.
Sau đây là một vài biểu hiện của Bloaters:
Long method
- Dấu hiệu nhận biết
Method chứa quá nhiều dòng code. Thường thì nếu method dài hơn 10 dòng thì ta nên xem xét việc xử lý nó ngay trước khi ta tự làm khó chính bản thân về sau này.
- Nguyên nhân
Nguyên nhân dẫn đến lỗi này khá quen thuộc và dễ nhận biết: code, chức năng mới được ta thêm vào mỗi ngày chứ không được lược bỏ đi bao giờ. Viết code bao giờ cũng dễ hơn đọc code, và nó chỉ “bốc mùi” khi ta nhận ra method giờ đã quá lớn và vượt khỏi tầm kiểm soát.
Về cơ bản, việc viết một method mới sẽ khó nhằn hơn việc thêm một vài dòng nhỏ tưởng chừng như vô hại, và sau nhiều lần nhỏ như thế, ta có một đống code bẩn.
- Giải pháp
Về nguyên tắc, nếu ta cần comment thứ gì đó phía bên trong một method, thì ta nên tách nó thành một method mới, và tên của method mới sẽ “thay điều muốn nói”.
- Ví dụ
Ban đầu ta có method sau:
void PrintOwing()
{
this.PrintBanner();
// Print details.
Console.WriteLine("name: " + this.name);
Console.WriteLine("amount: " + this.GetOutstanding());
}
Ta tách ra thành các method mới:
void PrintOwing()
{
this.PrintBanner();
this.PrintDetails(this.GetOutstanding());
}
void PrintDetails(double outstanding)
{
Console.WriteLine("name: " + this.name);
Console.WriteLine("amount: " + this.outstanding);
}
Large Class
- Dấu hiệu nhận biết
Khi một class chứa quá nhiều fields/methods hay chứa quá nhiều dòng code.
- Nguyên nhân
Khi mới bắt đầu, các class thường nhỏ. Nhưng cùng với thời gian, chúng ngày càng phình to khi project ngày càng được phát triển thêm.
Giống như Long methods, lập trình viên thường đi tắt bằng cách thêm một vài tính năng mới vào trong class đã có sẵn, thay vì tạo ra một class mới cho tương lai.
- Giải pháp
Về nguyên tắc, nếu class đủ lớn để phân tách, ta nên tách các chức năng của nó thành các component nhỏ hơn. Trong trường hợp class liên quan đến GUI, ta nên thử tách biệt phần data và behavior thành các phần riêng, đồng thời tạo những bản copy cho data, để chúng ở 2 nơi khác nhau để giữ cho dữ liệu nhất quán.
- Ví dụ
Đây là class Person ban đầu:
Giờ đây ta sẽ rảnh tay hơn khi có hẳn một class cho TelephoneNumber:
Primitive Obssesion (Lạm dụng dữ liệu nguyên thuỷ)
- Dấu hiệu nhận biết
Sử dụng dữ liệu nguyên thuỷ thay vì những object nhỏ cho những việc đơn giản (như đơn vị tiền tệ, khoảng cách, hay những string đặc biệc cho số điện thoại, …).
Thêm vào đó, ta cũng hay dùng constant cho các thông tin trong code (ví dụ như USER_ADMIN_ROLE = 1 để tượng trưng cho quyền của người quản trị).
- Nguyên nhân
Giống như các lỗi khác, lạm dụng các dữ liệu nguyên thuỷ này xuất hiện khi ta LƯỜI. Tạo thêm một hằng hoặc một biến kiểu nguyên thuỷ dễ hơn việc tạo một class mới rất rất nhiều. Và đời vẫn cứ thế trôi, các biến các hàm cứ lũ lượt thêm vào, và điều gì đến cũng đến: CLASS BỊ MẤT KIỂM SOÁT.
- Giải pháp
Nếu có một lượng khá lớn các thuộc tính, các biến, các hàm, việc cần làm là bỏ chúng nó vào các class khác nhau, với đúng chức năng và đóng gói lại.
Hoặc nếu có array trong class, ta nên chuyển chúng thành một class khác và implement các method cần thiết để nó hoạt động trơn tru.
- Ví dụ
string[] row = new string[2];
row[0] = "Liverpool";
row[1] = "15";
Performance row = new Performance();
row.SetName("Liverpool");
row.SetWins("15");
Ví dụ trên ta đã thay việc khai báo row bằng string[], thực chất là chuyển công việc đó vào trong class Performance. Và giờ đây code dễ đọc và có ý nghĩa hơn rất nhiều việc nhìn vào các index 0 và 1 vốn chẳng có liên quan gì.
Long Paramater List
- Dấu hiệu nhận biết
Khi một method có nhiều hơn 3 hoặc 4 paramaters, ta cần phải xử lý nó ngay. Thử tưởng tượng việc bạn phải kiểm soát một lượng lớn các paramater và nhầm lẫn thứ tự giữa chúng, sau đó tốn một khoảng thời gian kha khá chỉ để tìm ra lỗi ngữ nghĩa rất khó chịu (ví dụ như 2 param cùng kiểu dữ liệu, đặt cạnh nhau, nhưng thứ tự bị đảo ngược).
- Nguyên nhân
Một số lượng lớn các paramaters xuất hiện có thể vì ta đang cài đặt nhiều thuật toán lồng vào nhau ở trong một method. Và phải có các param này thì method mới hoạt động đúng cách.
Hoặc một trường hợp khá phổ biến khi ở phía trong một method, ta cần tạo một object mới(thứ cần những paramaters riêng). Vậy là để cho method hoạt động, ta cần truyền thêm cả những param riêng đó, và rồi danh sách các param cứ thế dài thêm.
- Giải pháp
Kiểm tra những param được truyền vào method, nếu các tham số này chỉ là kết quả của việc gọi các tham số khác, thì hãy bỏ ngay tham số đó đi và gọi nó khi cần thiết.
Thay vì gửi lũ lượt một loạt các param, ta có thể gói chúng lại vào trong một object và chỉ chuyền object này vào method thôi.
- Ví dụ
int low = daysTempRange.GetLow();
int high = daysTempRange.GetHigh();
bool withinPlan = plan.WithinRange(low, high);
bool withinPlan = plan.WithinRange(daysTempRange);
Ở ví dụ này, biến daysTempRange đã bao gồm trong nó cả hai param low và high, và nếu với số lượng param lớn hơn, thì đây là cách rất hữu hiệu để giảm thiểu nhầm lẫn không đáng có.
Data Clumps (Các “đống” dữ liệu)
- Dấu hiệu nhận biết
Đôi khi một vài phần của mã nguồn sẽ bao gồm một nhóm các biến liên quan đến việc kết nối dữ liệu. Và ta nên TÁCH PHẦN KẾT NỐI DỮ LIỆU RA THÀNH CÁC CLASS RIÊNG BIỆT.
- Nguyên nhân
Lỗi này cũng khá phổ biến, xuất hiện khi ta copy một đoạn code ở đâu đó, và “thần kì” thay, nó hoạt động.
Và chúng rải rác ở khắp nơi trong cả project, khi cần gì đó, lập trình viên chúng ta lại copy paste tiếp mà không tính đến việc nhất quán dữ liệu. Các nhóm dữ liệu khác nhau cũng bị chồng chéo và không dễ kiểm soát một chút nào.
- Giải pháp
Với lỗi loại này, chỉ cần tách riêng biệt các phần xử lý liên quan đến dữ liệu ra thành các class, và những class này chỉ thực hiện đúng các công việc tương tác với dữ liệu mà thôi.
Nguồn tham khảo: