💡 Key Takeaways
- Rule I Follow #1: Functions Should Do One Thing (But That Thing Can Be Bigger Than You Think)
- Rule I Follow #2: Names Should Reveal Intent (Even If They're Long)
- Rule I Follow #3: Comments Explain Why, Not What (But Use More Than You Think)
- Rule I Follow #4: Fail Fast and Fail Loud
Tôi đã nhìn chằm chằm vào mã của người khác suốt 14 năm qua với vai trò là kiến trúc sư phần mềm cao cấp tại một công ty fintech xử lý 2,3 tỷ đô la giao dịch hàng tháng. Ngày thứ Ba tuần trước, tôi đã xem xét một yêu cầu kéo khiến tôi thực sự phản ứng mạnh — 847 dòng trong một hàm duy nhất, các tên biến như "data2" và "temp," và bình luận có nội dung đúng nghĩa là "// điều kỳ diệu xảy ra ở đây." Nhà phát triển nào đã viết nó? Là một sinh viên tốt nghiệp ngành CS của Stanford với GPA 3.9.
💡 Những ĐIểm Chính
- Quy Tắc Tôi Tuân Theo #1: Các Hàm Nên Thực Hiện Một Việc (Nhưng Việc Đó Có Thể Lớn Hơn Bạn Nghĩ)
- Quy Tắc Tôi Tuân Theo #2: Các Tên Nên Tiết Lộ Ý Định (Ngay Cả Khi Chúng Dài)
- Quy Tắc Tôi Tuân Theo #3: Các Bình Luận Giải Thích Tại Sao, Chứ Không Phải Cái Gì (Nhưng Sử Dụng Nhiều Hơn Bạn Nghĩ)
- Quy Tắc Tôi Tuân Theo #4: Thất Bại Nhanh Chóng và Thất Bại Rõ Ràng
Đó là khi tôi nhận ra: chúng ta đã dạy mã sạch một cách sai lầm.
Mọi người thường trích dẫn "Mã Sạch" của Uncle Bob như thể nó là thánh thư. Họ thuộc lòng các nguyên tắc SOLID, tranh cãi về tab và khoảng trắng, và viết các hàm nhỏ đến mức cần một kính hiển vi để đọc được chúng. Nhưng đây là điều không ai nói với bạn: một số quy tắc đó đang làm mã của bạn trở nên tồi tệ hơn.
Tôi không ở đây để chỉ trích công việc của Robert Martin — nó rất cơ bản và quan trọng. Nhưng sau khi xem xét hơn 3.000 yêu cầu kéo, hướng dẫn 47 nhà phát triển, và duy trì một mã nguồn đã hoạt động từ năm 2011, tôi đã học được quy tắc nào thực sự quan trọng và quy tắc nào chỉ là kịch tính cho nhà phát triển. Để tôi cho bạn biết điều tôi muốn nói.
Quy Tắc Tôi Tuân Theo #1: Các Hàm Nên Thực Hiện Một Việc (Nhưng Việc Đó Có Thể Lớn Hơn Bạn Nghĩ)
Nguyên tắc "trách nhiệm duy nhất" cho các hàm có lẽ là quy tắc bị hiểu sai nhất trong mã sạch. Tôi thấy các nhà phát triển tạo ra các hàm dài ba dòng, với tên như "validateUserEmailFormat" được gọi bởi "validateUserEmail," mà lại được gọi bởi "validateUser." Nó giống như rùa tất cả đều xuôi dòng, và bạn cần mở bảy tập tin chỉ để hiểu điều gì đang xảy ra khi người dùng đăng ký.
Dưới đây là những gì tôi thực sự làm: tôi viết các hàm hoàn thành một hoạt động kinh doanh có ý nghĩa, không phải một hoạt động kỹ thuật. Khi tôi viết một hàm gọi là "processPayment," nó có thể dài 45 dòng. Nó xác thực phương thức thanh toán, kiểm tra gian lận, tạo bản ghi giao dịch và gửi email xác nhận. Đó là bốn hoạt động kỹ thuật, nhưng nó là một hoạt động kinh doanh: xử lý một khoản thanh toán.
Chìa khóa là mỗi bước đó được phân định rõ ràng trong mã. Tôi sử dụng dòng trống để tách các phần logic, và tôi thêm các bình luận ngắn gọn giải thích "tại sao" của mỗi phần. Khi nhà phát triển khác đọc "processPayment," họ có thể hiểu toàn bộ luồng thanh toán mà không phải nhảy qua mười hai tập tin khác nhau.
Tôi đã đo lường điều này một lần. Trong mã nguồn cũ của chúng tôi, nơi mà chúng tôi theo quy tắc "các hàm phải nhỏ" một cách nghiêm ngặt, nhà phát triển trung bình cần mở 8,3 tập tin để hiểu một luồng người dùng duy nhất. Sau khi tôi viết lại để sử dụng các hàm "hoạt động có ý nghĩa," con số đó giảm xuống còn 2,1 tập tin. Thời gian xem xét mã giảm 34%. Thời gian khắc phục lỗi giảm 28%.
Quy tắc không phải là "làm cho hàm nhỏ." Quy tắc là "làm cho hàm dễ hiểu." Đôi khi điều đó có nghĩa là 10 dòng. Đôi khi điều đó có nghĩa là 50. Nhưng điều đó không bao giờ có nghĩa là buộc các nhà phát triển phải đóng vai thám tử khắp mã nguồn của bạn chỉ để tìm hiểu cách mà một cú nhấp chuột hoạt động.
Quy Tắc Tôi Tuân Theo #2: Các Tên Nên Tiết Lộ Ý Định (Ngay Cả Khi Chúng Dài)
Tôi từng làm việc với một nhà phát triển đã nhất quyết rằng tên biến không bao giờ được vượt quá 15 ký tự vì "nó làm cho mã khó đọc hơn." Anh ta đã viết mã như thế này: "const usrPmtMthd = getUserPaymentMethod();" Tôi đã muốn ném bàn phím của mình ra ngoài cửa sổ.
"Mã tốt không phải là mã thông minh nhất—đó là mã mà nhà phát triển tiếp theo có thể hiểu được lúc 2 giờ sáng khi hệ thống đang gặp sự cố và khách hàng đang gọi."
Đây là quy tắc của tôi: nếu tôi không thể hiểu nội dung của một biến chỉ bằng cách đọc tên của nó một lần, thì tên đó đã sai. Tôi không quan tâm nếu nó dài 40 ký tự. Tôi thích đọc "userSelectedPaymentMethodFromDropdown" hơn là mất ba phút để tìm hiểu xem "pmtMthd" có nghĩa là gì.
Trong hệ thống xử lý thanh toán của chúng tôi, chúng tôi có một biến mang tên "transactionRequiresAdditionalFraudVerificationBasedOnUserHistory." Nó dài 71 ký tự. Nó cũng rất rõ ràng. Khi bạn thấy biến đó trong một câu lệnh if, bạn biết chính xác điều gì đang được kiểm tra và tại sao. Không cần bình luận. Không cần dịch tâm lý.
Phản biện mà tôi thường nghe là "nhưng tên dài làm cho các dòng quá dài!" Chắc chắn, nếu bạn vẫn đang giả vờ rằng chúng ta đang viết mã trên các máy tính 80 cột từ năm 1985. Chúng ta có màn hình 27-inch bây giờ. Hãy sử dụng chúng. Tôi đặt giới hạn chiều dài dòng của mình là 120 ký tự, và tôi chưa bao giờ gặp vấn đề gì về khả năng đọc.
Dưới đây là bài kiểm tra mà tôi sử dụng: nếu một nhà phát triển junior có thể đọc tên biến của bạn và ngay lập tức biết nó chứa nội dung gì, loại nào và đại khái được sử dụng cho mục đích gì, bạn đã đặt tên đúng. Nếu họ cần cuộn lên để xem nơi đã được khai báo hoặc kiểm tra định nghĩa loại, bạn đã thất bại.
Tôi đã thấy điều này cải thiện đáng kể chất lượng xem xét mã. Khi các tên rõ ràng, những người xem xét dành ít thời gian hỏi "điều này làm gì?" và nhiều thời gian hỏi "đây có phải là cách tiếp cận đúng không?" Đó là nơi có giá trị thực sự.
Quy Tắc Tôi Tuân Theo #3: Các Bình Luận Giải Thích Tại Sao, Chứ Không Phải Cái Gì (Nhưng Sử Dụng Nhiều Hơn Bạn Nghĩ)
Các nhà thuần túy về mã sạch sẽ nói với bạn rằng "mã tốt không cần bình luận." Họ đúng một nửa. Mã tốt không cần bình luận giải thích cái gì mà mã thực hiện. Nhưng nó hoàn toàn cần những bình luận giải thích tại sao mã tồn tại.
| Quy Tắc Mã Sạch | Sách Nói Gì | Cái Gì Thực Sự Hoạt Động | Khi Nào Thì Phá Vỡ Nó |
|---|---|---|---|
| Chiều Dài Hàm | Giữ các hàm dưới 20 dòng | Giữ các hàm dưới một màn hình (40-60 dòng) | Khi việc tách ra tạo ra nhiều sự nhầm lẫn hơn là rõ ràng |
| Bình Luận | Mã nên tự tài liệu | Giải thích tại sao, không phải cái gì | Các thuật toán phức tạp, quy tắc kinh doanh, hoặc các quyết định không rõ ràng |
| Nguyên Tắc DRY | Không bao giờ lặp lại bản thân | Đừng lặp lại bản thân chưa (chờ đến lần xuất hiện thứ ba) | Khi sự trừu tượng kết hợp các tính năng không liên quan |
| Tên Biến | Luôn luôn sử dụng các tên mô tả | Ngữ cảnh quan trọng: 'i' thì ổn trong vòng lặp, 'userAuthenticationToken' trong các phạm vi nhỏ thì là thừa | Các biến đếm vòng lặp, các viết tắt được biết đến trong ngữ cảnh miền |
Tháng trước, tôi đã tìm thấy một hàm trong mã của chúng tôi có một giải pháp kỳ lạ: nó đã thêm một độ trễ 50 mili giây trước khi xử lý một webhook. Không có bình luận. Không có giải thích. Chỉ đơn giản là "await sleep(50);" ngồi đó như một mìn đất. Tôi đã dành hai giờ để cố gắng tìm hiểu xem đó có phải là một lỗi hay không. Thực tế là đó là một sự giải quyết cho một điều kiện đua trong một API bên thứ ba mà chúng tôi phát hiện ra sau ba ngày gỡ lỗi trong môi trường sản xuất.
Giờ đây, mã đó có một bình luận: "// Độ trễ 50ms là cần thiết: webhook của Stripe có thể đến trước khi API của họ phản ánh trạng thái giao dịch. Phát hiện trong sự cố #2847 vào ngày 15-08-2023. Xóa bình luận này sau khi Stripe sửa lỗi vấn đề nhất quán cuối cùng của họ (thẻ #45892)."
🛠 Khám Phá Công Cụ Của Chúng Tôi
Đó là một bình luận tốt. Nó giải thích tại sao mã tồn tại, tham chiếu đến sự cố đã gây ra nó, và thậm chí cung cấp một con đường để loại bỏ nó trong tương lai. Nếu không có bình luận đó, nhà phát triển tiếp theo sẽ xóa nó nghĩ rằng đó là một sai lầm.
Tôi thêm bình luận một cách hào phóng trong ba tình huống: khi tôi làm việc quanh một lỗi trong một phụ thuộc, khi tôi thực hiện một quy tắc kinh doanh không rõ ràng, và khi tôi tối ưu hóa cho hiệu suất theo cách làm cho mã khó đọc hơn. Trong mỗi trường hợp, bình luận giải thích ngữ cảnh mà không thể nhìn thấy trong chính mã.