Series Clean Code Thực chiến #5: Nghệ thuật thiết kế Lớp (Classes) – Bản thiết kế hoàn hảo
1. Cấu trúc chuẩn mực của một Lớp (Quy tắc Tờ báo)
Khi bạn mở một tờ báo ra đọc, tiêu đề ở trên cùng sẽ tóm tắt nội dung, đoạn mở đầu cung cấp ý chính, và các chi tiết tỉ mỉ nhất sẽ nằm ở phần dưới cùng. Code của một Class cũng phải đọc y hệt như vậy.
Thứ tự sắp xếp chuẩn xác trong một file Class (như PHP/Laravel hay Golang struct):
- Danh sách Biến (Properties): Public constants -> Private static variables -> Private instance variables.
- Hàm khởi tạo (Constructor).
- Các Hàm Public (Public Methods): Những hàm này chính là "Tiêu đề tờ báo", là API mở ra cho thế giới bên ngoài gọi vào.
- Các Hàm Private (Private Utility Methods): Bất kỳ hàm public nào gọi đến một hàm private, thì hàm private đó phải được đặt ngay bên dưới nó.
Nhờ cấu trúc này, khi ai đó mở file TransactionController.php của bạn, họ chỉ cần đọc từ trên xuống dưới là hiểu được bức tranh tổng thể mà không cần phải cuộn chuột lung tung để tìm hàm hỗ trợ.
2. Kích thước của Lớp: Nguyên lý Trách nhiệm Đơn lẻ (SRP)
Với Hàm, chúng ta đếm số dòng để đo kích thước. Nhưng với Lớp, chúng ta đo bằng "Trách nhiệm" (Responsibilities).
Nguyên lý SRP (Single Responsibility Principle) phát biểu: "Một Lớp chỉ nên có MỘT lý do duy nhất để thay đổi."
Lớp "Đa nhân cách" (God Class): Hãy xem class TurnstileManager (Quản lý cổng quay) dưới đây:
class TurnstileManager {
public function openGate($turnstileId) { ... }
public function closeGate($turnstileId) { ... }
// Đột nhiên lại có hàm tính doanh thu ở đây???
public function calculateDailyRevenue($stationId) { ... }
// Lại còn gửi email báo cáo???
public function sendReportToManager($email) { ... }
}
Class này có tới 3 lý do để thay đổi:
- Khi logic đóng/mở cổng thay đổi.
- Khi công thức tính tiền thay đổi.
- Khi format gửi email thay đổi. Nó ôm đồm quá nhiều việc và sẽ tạo ra những xung đột code (merge conflicts) liên tục khi làm việc nhóm.
Clean Code (Tách Lớp): Hãy chẻ nó ra thành 3 Lớp riêng biệt: TurnstileController (Điều khiển thiết bị), RevenueCalculator (Tính toán tiền bạc) và ReportNotifier (Gửi thông báo). Hệ thống của bạn sẽ cực kỳ dễ bảo trì.
3. Định luật Demeter (Đừng nói chuyện với người lạ)
Đây là lỗi phổ biến nhất khiến các thành phần trong hệ thống dính chặt vào nhau (Tight Coupling). Định luật Demeter yêu cầu một module không nên biết về cấu trúc bên trong của các đối tượng mà nó thao tác.
Lỗi "Đoàn tàu trật bánh" (Train Wreck):
// Lấy thẻ -> Lấy Chủ thẻ -> Lấy Ví tiền -> Trừ tiền
$card->getOwner()->getWallet()->deduct(15000);
Đoạn code trên nhìn có vẻ sành điệu, nhưng nó là một thảm họa. Class hiện tại phải "biết" rằng Thẻ chứa Chủ thẻ, Chủ thẻ chứa Ví tiền. Nếu ngày mai Database thay đổi, Ví tiền gắn trực tiếp vào Thẻ chứ không qua Chủ thẻ nữa, toàn bộ hệ thống sẽ gãy đổ.
Clean Code (Tell, Don't Ask - Ra lệnh, đừng hỏi): Bạn chỉ nên giao tiếp với "bạn bè trực tiếp". Thay vì chọc sâu vào cấu trúc bên trong, hãy ra lệnh cho đối tượng làm việc của nó.
// Giao hẳn trách nhiệm trừ tiền cho cái Thẻ. Bên trong nó tự xử lý.
$card->chargeFare(15000);
4. Tính Gắn kết cao (Cohesion)
Một Lớp được coi là có tính gắn kết cao nếu nó có một số lượng nhỏ các biến instance (thuộc tính), và mọi hàm trong lớp đó đều thao tác với một hoặc nhiều biến này.
Nếu bạn có một Lớp chứa 5 biến, nhưng có một hàm chỉ dùng duy nhất biến thứ 5 và không bao giờ đụng đến 4 biến kia, thì đó là dấu hiệu rõ ràng cho thấy cái hàm đó (và cái biến đó) đang "nằm nhầm nhà". Bạn cần tách chúng ra thành một Lớp mới.
Bằng việc thiết kế những Lớp nhỏ gọn, làm đúng một việc và che giấu cấu trúc dữ liệu bên trong, bạn sẽ xây dựng được những module mã nguồn độc lập. Giống như các khối Lego, bạn có thể dễ dàng rút một khối ra sửa chữa và lắp lại mà không làm sập toàn bộ lâu đài.
All rights reserved