Đọc mã nguồn Laravel: Tại sao lại tồn tại hàm options() trông có vẻ "thừa thãi" này?
Chào anh em cộng đồng Viblo!
Khi rảnh rỗi ngồi "đọc ruột" (source code) của các framework lớn như Laravel, thỉnh thoảng chúng ta sẽ bắt gặp những đoạn code trông cực kỳ vô lý hoặc có vẻ như... thừa thãi. Một trong số đó là đoạn code ngắn ngủn này:
public function options() {
return $this->option();
}
Mới nhìn qua, chắc chắn nhiều anh em sẽ tự hỏi: "Tại sao phải mất công định nghĩa thêm một cái hàm options() (số nhiều), chỉ để bên trong nó return lại chính cái hàm option() (số ít)? Sao không dùng luôn hàm gốc cho rồi?"
Thực chất, đằng sau 4 dòng code tưởng chừng vô thưởng vô phạt này là cả một triết lý thiết kế API cực kỳ sâu sắc của Taylor Otwell (cha đẻ Laravel) hướng tới Developer Experience (Trải nghiệm lập trình viên). Hôm nay, chúng ta cùng mổ xẻ nó nhé!
1. Nguồn gốc của đoạn code: Nằm ở đâu?
Đoạn code này nằm trong class Illuminate\Console\Command của Laravel – core class đứng sau mọi câu lệnh Artisan mà anh em vẫn gõ hàng ngày.
Trong Laravel Console, khi bạn chạy một lệnh như:
php artisan report:generate --type=daily --force
Bạn sẽ có 2 option là type và force. Để lấy giá trị của chúng trong code, hàm gốc được thiết kế là hàm option($key = null):
- Nếu bạn truyền
$keyvào:$this->option('type'), nó trả về chuỗi'daily'. - Nếu bạn KHÔNG truyền gì vào:
$this->option(), nó hiểu là bạn muốn lấy tất cả, và trả về một mảng (array) chứa toàn bộ options:['type' => 'daily', 'force' => true].
Về mặt logic máy tính, thiết kế hàm option($key = null) là đã quá đủ để giải quyết trọn vẹn bài toán. Không cần thêm bất kỳ hàm nào khác.
2. Ngữ nghĩa học (Semantics) - Cuộc chiến của số ít và số nhiều
Tuy logic máy tính đã thỏa mãn, nhưng về mặt ngôn ngữ con người thì nó lại có vấn đề.
Hãy thử đọc to dòng code này lên:
$allOptions = $this->option();
Cảm giác rất "cấn" đúng không? option (không có s) là một danh từ số ít. Việc gọi một hàm số ít nhưng lại mong đợi nó trả về một mảng chứa nhiều phần tử (số nhiều) vi phạm nghiêm trọng quy tắc đọc hiểu tự nhiên trong Clean Code.
Khi một lập trình viên khác đọc code của bạn, thấy chữ option() không truyền tham số, họ có thể sẽ khựng lại vài giây để suy nghĩ: "Hàm này trả về cái gì? Nó lấy option mặc định à? Hay nó bị lỗi thiếu tham số?"
Đó là lý do hàm options() ra đời:
// Trả về MỘT giá trị (Singular)
$type = $this->option('type');
// Trả về NHIỀU giá trị (Plural)
$allOptions = $this->options();
3. Syntactic Sugar (Cú pháp kẹo ngọt) và Developer Experience (DX)
Hàm options() đóng vai trò là một Alias (bí danh), hay còn gọi là Syntactic Sugar. Nó không mang thêm bất kỳ logic tính toán mới nào, nó chỉ tồn tại để làm cho mã nguồn của người sử dụng framework trở nên "ngọt ngào", trôi chảy và dễ đoán hơn.
Trong các dự án lớn, việc thiết kế API/Interface không chỉ dừng lại ở việc "chạy đúng", mà còn phải "dễ dùng". Một API tốt là một API mà lập trình viên có thể đoán được chức năng của nó ngay từ cái tên mà không cần phải bấm tổ hợp phím Ctrl + Click để chui vào tận file core đọc document.
Bằng cách tạo ra một wrapper method (hàm bọc) chỉ với 4 dòng code, Laravel đã:
- Bảo vệ được luồng logic cốt lõi của hàm
option($key = null)bên dưới. - Cung cấp một interface hoàn hảo về mặt ngữ pháp cho lập trình viên ở tầng trên.
Lời kết: Áp dụng vào dự án thực tế thế nào?
Bài học rút ra từ đoạn code này là sự thỏa hiệp hợp lý giữa sự tối giản (minimalism) và tính rõ ràng (clarity).
Trong dự án thực tế của anh em, nếu có những hàm sử dụng tham số mặc định (default parameters) dẫn đến sự thay đổi lớn về kiểu dữ liệu trả về (ví dụ: truyền ID thì trả về 1 Object, không truyền ID thì trả về 1 Array), hãy mạnh dạn áp dụng triết lý này. Đừng ngại viết thêm một hàm bọc (wrapper) vài ba dòng để tạo ra những cái tên số ít/số nhiều rõ ràng.
Code dài thêm vài dòng không làm hệ thống của bạn chậm đi một mili-giây nào, nhưng nó sẽ tiết kiệm cho đồng nghiệp của bạn (và cả chính bạn của 6 tháng sau) rất nhiều nơ-ron thần kinh khi ngồi đọc lại code đấy!
All Rights Reserved