Clean code #3: Viết các điều kiện (Conditionals) (P1)
1. Nguyên tắc khi viết câu điều kiện:
Có 4 nguyên tắc đơn giản cần cân nhắc khi viết câu điều kiện:
a. Clear Intent:
- Câu điều kiện tốt truyền đạt rõ ràng ý định.
- Cũng giống như trong cuộc sống, chỉ hiểu sơ sơ là chúng ta sẽ rẽ phải hay rẽ trái ở ngã ba đường là chưa đủ. Thực sự hữu ích khi hiểu tại sao chúng ta lại quyết định đi theo một hướng nhất định ngay từ đầu.
- Tuy nhiên, nếu các nhà phát triển tạo ra biển báo đường bộ, nhiều biển báo sẽ hữu ích như thế này. Chắc chắn, chúng ta có thể thấy có sự lựa chọn giữa các hướng, nhưng không rõ mỗi con đường dẫn đến đâu và sẽ lựa chọn giữa các lựa chọn của mình như thế nào. Cách diễn đạt này khó hiểu và không hữu ích. Hiểu được ý định của lập trình viên ban đầu là vấn đề khó khăn nhất.
Understanding the original programmer’s intent is the most difficult problem. (Fjelstad & Hamlen)
b. Use the right tool:
- Việc lựa chọn đúng công cụ là nền tảng để viết code sạch như đã đề cập ở Right tool for the job
- Với rất nhiều cách truyền tải mã theo các hướng khác nhau, điều quan trọng là phải nghĩ đến người đọc khi lựa chọn cách tiếp cận.
c. Bite-size logic:
- Các câu điều kiện được viết tốt cung cấp logic ngắn gọn mà bộ não hữu hạn của chúng ta có thể dễ dàng hiểu ngay lập tức.
- Chúng ta sẽ xem xét các phương pháp để biến các câu điều kiện dài, phức tạp thành các câu lệnh rõ ràng, đơn giản.
d. Sometimes code isn’t the answer:
Đôi khi, mã không phải là câu trả lời. Và một câu điều kiện có thể bị loại bỏ hoàn toàn.
2. Viết câu điều kiện so sánh Boolean:
Boolean là lựa chọn hiển nhiên khi cần xử lý quyết định true/false. Tuy nhiên, lập trình viên thường so sánh Boolean với true và false một cách rõ ràng. Điều này hoàn toàn không cần thiết và thực tế nó chỉ làm tăng thêm độ nhiễu cho câu điều kiện trong hầu hết các trường hợp.
🔴 Dirty
if(loggedIn == true) {
// do something
}
🟢 Clean
if(loggedIn) {
// do something
}
Lưu ý: Phiên bản Clean sẽ đọc giống như tiếng Anh. Bạn sẽ nói gì trong cuộc sống thực? "Nếu loggedIn bằng true", hay "Nếu bạn đã loggedIn chưa?"
Phiên bản Dirty làm cho người đọc code phải khựng lại 1 chút và suy nghĩ "Nếu loggedIn bằng true" thì code bên dưới sẽ được thực thi, có thể gây sự không tự nhiên lắm cho người đọc code. Code phải tự nhiên, đọc như là nói thầm trong đầu.
Trong câu điều kiện so sánh Boolean, bạn hãy cố gắng đặt tên biến điều kiện như đang hỏi người đọc càng tốt. Ví dụ: isActive, isEnable, isShown, ...
3. Boolean Assignments:
Mục tiêu chính khi clean code là tăng signal to noise => Vì vậy Boolean cũng nên được gán ngầm định.
🔴 Dirty
if(ageOfUser > 18) {
isValidAge = true;
} else {
isValidAge = false;
}
🟢 Clean
bool isValidAge = ageOfUser > 18;
Nguyên tắc cho phép gán Boolean
1. Ít dòng hơn
Với Ví dụ Dirty ở trên, chúng ta có thể thấy rằng mỗi Boolean được gán rõ ràng cho giá trị true hoặc false. Điều này là không cần thiết.
Với Ví dụ Clean, việc đọc các đoạn mã ngắn trở nên dễ dàng hơn. Và càng nhiều tín hiệu chúng ta có thể thấy trên màn hình cùng một lúc thì càng tốt (nhưng câu lệnh cũng ko nên quá dài so với màn hình chúng ta đang sử dụng).
2. Không có khởi tạo riêng biệt
Trong ví dụ Dirty, biến phải được khởi tạo riêng biệt với mục đích sử dụng của nó trong if và else bên dưới, một dòng nữa trên màn hình sẽ bị lãng phí.
3. Không lặp lại
Ngoài ra còn có nguy cơ xảy ra lỗi cao hơn vì biến phải được lặp lại trên ba dòng riêng biệt trong ví dụ Dirty.
4. Đọc code phải như lời nói
Phiên bản Clean sẽ đọc rất giống lời nói. Chúng ta hãy đọc to từng đoạn mã và xem chúng khác nhau như thế nào.
Ví dụ Clean: Tuổi hợp lệ là nếu tuổi của user lớn hơn 18.
Ví dụ Dirty: Nếu tuổi của user lớn hơn 18 thì đó là tuổi hợp lệ, nếu không thì đó là tuổi không hợp lệ.
Nếu tôi nói như vậy ngoài đời thực, có lẽ bạn sẽ thấy tôi buồn cười vì tôi nói thừa. Chỉ là ngượng ngùng thôi. Dirty code nói to thường nghe có vẻ dài dòng, không tự nhiên và lạ lẫm trong đời thực.
4. Positive Conditionals:
Bộ não của chúng ta thường có xu hướng lý giải câu điều kiện khẳng định tốt hơn câu điều kiện phủ định. Các yêu cầu tiêu cực thường sẽ tạo ra sự xáo trộn trong tâm trí bạn. Vì vậy bạn nên sử dụng các điều kiện khẳng định.
🔴 Dirty
if(!isNotLoggedIn) {
// do something
}
Tôi chắc chắn bạn sẽ đồng ý rằng ví dụ Dirty này gây nhầm lẫn và đòi hỏi phải suy nghĩ cẩn thận để hiểu được hàm ý và mục đích của code. Hãy xem ví dụ Clean sẽ để so sánh.
🟢 Clean
if(loggedIn) {
// do something
}
Câu này đọc giống như lời nói nên rất dễ hiểu.
❌ Điều kiện phủ định thường là dấu hiệu của việc lập trình vô tình. Bạn có thể tưởng tượng các developer đang viết mã và mã không hoạt động như mong đợi. Vì vậy, sau đó anh ta thêm dấu chấm than (!) vào điều kiện để phủ định Boolean và có vẻ như code bây giờ đã work tốt. Công việc đã hoàn thành. Nhưng nếu anh ta hoặc đồng nghiệp của anh ta không review lại code, thì vài tháng sau code ở đây đọc thật là dễ nhầm lẫn và đòi hỏi phải suy nghĩ thật cẩn thận để code tiếp.
Tóm lại: Nếu bạn kết thúc bằng một câu điều kiện phủ định, hãy cố gắng cấu trúc lại câu điều kiện đó để tạo ra một câu điều kiện khẳng định thay vì phủ định.
5. Ternary Elegance:
Ở trong các bài viết trước, chúng ta đã được khuyến khích viết code thật ngắn gọn, xúc tích để làm tăng tính dễ hiểu. Các ngôn ngữ lập trình hiện nay cũng hỗ trợ cho các lập trình viên cú pháp để làm gọn các Ternary trong code. Ví dụ:
🔴 Dirty
int employeeBonus = 0;
if(isEmployee) {
employeeBonus = 100;
} else {
employeeBonus = 0;
}
🟢 Clean
int employeeBonus = isEmployee ? 100 : 0;
Cách tiếp cận này có nhiều lợi ích tương tự như phần 3. Boolean Assignments mà chúng ta vừa thảo luận. Nó cần ít dòng hơn nhiều và không yêu cầu phải lặp lại biến đang được gán.
Lưu ý Ternary operator chỉ yêu cầu một tham chiếu duy nhất đến biến employeeBonus.
Ví dụ Dirty ở trên tạo ra ba tham chiếu riêng biệt, bao gồm yêu cầu một dòng chuyên dụng để khởi tạo biến employeeBonus. Đó là ba cơ hội khác nhau cho lỗi đánh máy và ba tham chiếu khác nhau mà người đọc phải đọc. Nếu người đọc không quan tâm đến những gì được thiết lập cho biến employeeBonus, thì chỉ cần bỏ qua dòng bên phải.
Ternary operator cung cấp cú pháp rất thanh lịch và ngắn gọn. Nó tôn trọng nguyên tắc DRY. (DRY là viết tắt của Don't Repeat Yourself.). Tuy nhiên, những người viết code sạch cũng phải nhớ thuật ngữ YAGNE (YAGNE là viết tắt của You Ain't Going To Need It (Bạn sẽ không cần đến nó đâu). Chúng ta không nên đưa thêm độ phức tạp vào mã của mình chỉ vì chúng ta nghĩ rằng chúng ta có thể cần nó trong tương lai. Chỉ nên thêm độ phức tạp bổ sung khi thực sự cần thiết.
Lưu ý: Và cũng giống như bất kỳ công cụ nào, toán tử Ternary có thể bị lạm dụng. Bạn nên tránh sử dụng nhiều toán tử ternary trong một điều kiện duy nhất vì nó làm giảm khả năng đọc.
6. Stringly Typed:
🔴 Dirty
if(employeeType == 'manager') {
// do something
}
🟢 Clean
if(employee.Type == EmployeeType.Manager) {
// do something
}
Bạn có thể sử dụng enum để làm một strongly typed cho loại nhân viên ở trên. Cách tiếp cận này mang lại các lợi ích như bên dưới:
a. Strongly typed => No typos:
Strongly typed có nghĩa là không có cách nào để mắc lỗi đánh máy. (Nếu bạn mắc lỗi đánh máy, ứng dụng sẽ không biên dịch được. Bạn không thể chạy nó)
Trong ví dụ Dirty, nếu tôi gõ manger ở đây thay vì manager tôi không thể nào nhận ra chương trình hoạt động sai cho đến khi tôi test lại chương trình và chạy qua dòng code đó. Nhưng ở ví dụ Clean tôi sẽ biết ngay lỗi khi tôi sẽ thấy IDE của mình báo không thể tìm thấy manger. Ai mà biết được tại thời điểm nào thì rõ ràng là tôi đã nhập sai thông tin và nhập sai dòng đó.
b. Intellisense support:
Bạn sẽ có được sự hỗ trợ của Intellisense khi bạn Strongly typed. Intellisense giúp chúng ta hoàn thành câu, do đó IDE sẽ bật lên, cung cấp cho bạn ngữ cảnh, bạn không cần phải nhập nhiều để hoàn thành tất cả những việc này. Ở ví dụ Clean, bạn chỉ cần nhập EmployeeType., IDE đã suggest 1 danh sách type mà bạn muốn gõ (ở đây là .Manager ) việc của bạn là chỉ cần nhấn Tab trên bàn phím, IDE sẽ tự hoàn thành việc nhập của bạn.
c. Documents states:
Nó cũng giúp ghi lại các trạng thái tiềm năng. Vì vậy, EmployeeType có một số lượng tùy chọn hữu hạn nhất định và manager là một trong số đó, và tôi có thể thấy những tùy chọn khác nhau đó và điều đó giúp tôi lý luận, suy luận tốt hơn về code khi tôi đọc trên một cơ sở mã mới. Bạn không thể làm điều đó với những chuỗi này (Ví dụ: "manager", "boss", vv...). Tôi sẽ phải tìm kiếm xung quanh và cố gắng tìm tất cả những nơi mà EmployeeType đó được tham chiếu và xem liệu tôi có thể cảm nhận được tất cả các giá trị tiềm năng hay không. Điều này thật sự khó khăn.
Ví dụ Clean nó cũng giúp tôi document tài liệu tốt hơn, khi tôi đọc code và biết rằng danh sách EmployeeType của cty có thể chỉ có những type này trong danh sách (Ví dụ: "manager", "leader", "staff") và không thể có option type nào khác ngoài các loại trên. Còn ở ví dụ Dirty, tôi phải tìm cả project và thống kê lại các EmployeeType mà project này có, và nhiều khi 1 lập trình viên khác thêm 1 type khác vào, hoặc Product Document chỉnh sửa thêm hoặc xoá 1 EmployeeType thì việc chỉnh sửa code sẽ thật là khó khăn.
d. Searchable:
Hãy tưởng tượng rằng bạn có một ứng dụng đang làm việc với dữ liệu quản lý, đó là một vấn đề lớn nếu bạn làm theo ví dụ Dirty, vì nếu tôi tìm kiếm từ manager, tôi sẽ tìm thấy nó được liệt kê ở khắp mọi nơi. Có khả năng nó còn có trong các comment hoặc trong các tên biến khai báo có từ manager, ở các khu vực khác hoàn toàn không liên quan đến. Trong khi tôi chỉ đang cố gắng tìm ra tần suất tôi tìm kiếm một EmployeeType nhất định.
Ở ví dụ Clean, sử dụng enum, tôi có thể dễ dàng tìm kiếm tất cả các vị trí mà enum này được tham chiếu. ID của tôi sẽ chỉ giúp tôi, và tôi sẽ nhanh chóng xác định được ví trí tôi muốn khi tôi làm như vậy. Điều này trái ngược với tìm kiếm chuỗi, đặc biệt là nếu có một số lỗi đánh máy ở một số chỗ, tìm kiếm bằng chuỗi sẽ không hiển thị. Vì vậy, có rất nhiều lợi ích khi sử dụng Strongly typed, nếu ngôn ngữ của bạn hỗ trợ.
Vì phần này khá dài, nên nó sẽ được chia nhỏ ra. Chúng ta sẽ đến tiếp với phần 2 Clean code #3: Viết các điều kiện (Conditionals) (P2)
All rights reserved
Bình luận