Trong bài trước, tôi đã đề cập đến nhầm lẫn cơ bản dẫn đến hiểu sai về đơn nhiệm. Trong bài này, tôi muốn đưa ra một cách tiếp cận để giúp chúng ta có thể đạt được tính đơn nhiệm trong những dòng code của mình. Trước tiên, hãy chú ý đến các điểm sau:
Đơn nhiệm không chỉ áp dụng cho class
Trong định nghĩa về đơn nhiệm chỉ nói về class, nhưng đơn nhiệm còn áp dụng cho các khía cạnh nhỏ hơn như hàm, tên biến (cũng như các khía cạnh lớn hơn như gói thư viện). Điều này không có gì bất ngờ bởi người đưa ra khái niệm SOLID cũng là người viết cuốn Clean Code: A Handbook of Agile Software Craftsmanship, và trong cuốn sách đó là sự trình bày về tính đơn nhiệm của các hàm, các biến. Đơn nhiệm cũng không gói gọn trong lập trình hướng đối tượng mà cho mọi mô típ lập trình khác.
Nhìn từ đáy giếng lên là không hiểu quả
SOLID là các nguyên tắc thiết kế, nhưng nếu bạn chưa có đủ các kĩ năng, kinh nghiệm cần thiết về thiết kế, cố gắng nắm bắt nó qua các thiết kế, trừu tượng hóa, hay tìm các dấu hiệu của nó trong mã nguồn là hành động tù mù, vô ích. Hãy nghĩ đến con ếch dưới giếng nói về bầu trời. Bạn không có gì phải tự ái vì tất cả mọi người đều có khởi đầu như nhau. Nhận ra mình là con ếch tức là hiểu mình cần phải TRÈO ra khỏi cái giếng. TRÈO là một quá trình tích lũy, vì thế hãy bình tĩnh, bầu trời trên miệng giếng sẽ dần rộng ra khi ta trèo cao hơn, cho đến khi chúng ta có thể nhìn thấy cả bầu trời.
Từ các điểm trên, và từ kinh nghiệm của tôi, tôi khuyên các bạn hãy bắt đầu
Đặt tên cho cẩn thận
Tên biến, tên hàm, tên lớp hay bất cứ tên gì, hãy suy nghĩ thấu đáo về nó để nó có cái tên tốt nhất có thể. Mỗi cái tên đều thể hiện vai trò của thực thể trong mã nguồn. Vai trò ấy phải rõ ràng, dễ hiểu, và ĐƠN NHIỆM.
Chúng ta vẫn thường khai báo một biến count và dùng nó để đếm mọi thứ trong hàm. Chúng ta cũng biết nó tiềm ẩn rủi ro nếu không reset lại giá trị mỗi lần đếm thực thể mới. Đến khi nào bạn sẽ đặt tên cho nó là userCount, orderCount, pageCount thay cho count? Bạn sẽ dùng count để đếm cả user, order, page, nhưng chắc chắn chỉ dùng userCount để đếm user. Bạn cho rằng dùng một biến count thì tối ưu hơn thì tôi xin phản bác. Việc reset giá trị một biến về 0 hay việc cấp phát thêm một ô nhớ trong Stack là như nhau. 4 bytes so với 8 bytes, 12 bytes không đáng kể đối với bộ nhớ, so với lợi ích đem lại từ mã nguồn sạch sẽ, dễ đọc, dễ bảo trì.
Khi nào bạn sẽ đặt tên biến allUsers cho kết quả của một hàm getAllUsers thay vì tùy tiện như là users hay thậm chí items, records? activeUsers cho hàm getUsers(active: true)?
Một cách đặt tên phổ biến là result cho kết quả trả về của một hàm. Khi nào bạn sẽ muốn nó có cái tên ý nghĩa hơn? (mặc dù result vẫn là một cách đặt tên được chấp nhận phổ biến).
Một cách thực hành, thì bạn không chỉ đặt lại tên cho biến hay hàm. Bạn thực sự quy định lại cách sử dụng hàm hay biến đó. Như ví dụ về biến count bên trên, hoặc hàm getUsers(active: true). Khi nghĩ thấu đáo về hàm này, có thể bạn sẽ bỏ nó và tạo hàm getActiveUsers(). Sự khác biệt giữa getUsers(params) và getActiveUsers() là rất rõ ràng. getUsers(params) rất dễ mất kiểm soát vì nó như là một hàm tiện ích. Nó sẽ được gọi từ nhiều nơi, cũng như được sửa đổi nhiều lần (thêm parameter mới để thực hiện yêu cầu mới). Kể cả khi bắt đầu, chủ đích là để lấy active user và bạn tạo hàm getUsers(isActive: boolean), không có gì đảm bảo thế hệ tiếp theo của hàm không là getUsers(isActive: boolean, isInternalOnly: boolean = false). Cái tên getActiveUsers lại làm được việc đảm bảo sự tuân thủ trong mã nguồn mà không cần tài liệu hay quy định nào thêm.
Hãy chú ý điều này:
Mọi mã nguồn tốt đều có những cái tên tốt. Mọi mã nguồn SOLID cũng đều có những cái tên tốt.
Khi bạn còn những cái tên dở thì thật khó tưởng tượng bạn lại đạt được những điều như là SOLID. Cũng thật khó tưởng tượng mã nguồn có những class đơn nhiệm mà cái tên lại không liên quan, hay class được đặt tên tốt mà hàm và biến bên trong lại có tên dở. Con đường để có cái tên class tốt thì khá rõ ràng: tên biến, hàm tốt trước đã. Trước khi bạn được chắp bút thiết kế các interface, các class, các gói, bạn đã phải thể hiện tốt qua các hàm, các biến. Đó là sự rèn luyện cần thiết và dễ tiếp cận đối với lập trình viên, mà theo tôi nghĩ là rất hiệu quả. Bạn sẽ phải làm quen với việc xem xét một thành phần trong bức tranh tổng thể. Hàm này sẽ sử dụng ở đây hay còn ở đâu? Tương lai nó có cần cập nhật gì và cập nhật ấy có nên cho phép hay không? Biến này ở đây đã đầy đủ ý nghĩa chưa, liệu có thể gây hiểu nhầm trong class hay không? Những câu hỏi ấy đều là những câu hỏi phát sinh khi bạn thiết kế một class. Rèn luyện để thấu hiểu vai trò cũng nhưng sự tương tác của các thành phần trong một nhóm, là rèn luyện óc phân tích, đi vào chi tiết, chính là kĩ năng của một nhà thiết kế.
Đặt tên là một bước nhỏ nhưng ý nghĩa lớn. Ngay cả các lập trình viên dày dạn vẫn gặp nhiều vấn đề. Những cái tên đôi khi vẫn làm nổ ra tranh cãi trong Code Review. Ở góc độ của một senior thì tôi thường thấy sự nông cạn của lập trình viên chính là qua những cái tên. Nó thể hiện lập trình viên có hiểu chương trình hay không, có hiểu mã nguồn và hiểu công việc đang làm hay không. Một cái tên tốt chứng tỏ chiều sâu trong suy nghĩ của lập trình viên và ẩn chứa nhiều ý tứ. Hãy nghĩ về ví dụ getUsers và getActiveUsers và ý tứ đăng sau nó. Bạn sẽ thấy một chữ Active lại có ảnh hưởng lớn đến thế nào, và hãy liên hệ ảnh hưởng ấy với Đơn nhiệm.