+2

Clean code #6: Viết comment (P2)

Cùng tiếp tục sau Clean code #6: Viết comment (P1), chúng ta sẽ đến với Phần 2

7. Zombie Code

Hãy cùng xem xét ví dụ bên dưới:

protected void loadScreen(Object user)
{
    if (!IsUserOnline) {
        Screen.RegisterStartupPhoneCall(this.GetType(), "phone number", "calling();");
        String phoneNumber = user.phoneNumber;
        Screen.callingPhoneNumber();
    }
     if (Screen.IsUserOnline) {
        imgSendEmail = false;
        imgSendNotification = false;
    }
    
    //HttpClientRequest request = HttpClientRequest.Create('http://api.host.info/get_info.json") as HttpClientRequest
    // HttpResponse response = request.GetResponse();
    // DataJsonSerializer serializer = DataJsonSerializer();
    // return serializer.fromJson();
    
    // user.email = "abc@gmail.com"
    // user.address = "123/14 Abc stret"
    
    // labelEmail = user.email;
}

/// <summary>
/// Set user id to send email or notify
/// we'll get user id to user object
/// user object is get from Back-end
/// we need call API and parse json from response of Back-end
/// </summary>
private void SetUserId() {
    userId = user.id;
}

// protected void SendEmailButton() 
// {
 //    Screen.setColorButton();
//     Screen.setTextButton();
//     Screen.setEventButton();
 //     Screen.loadButto();
// }

Với ví dụ trên, chúng ta không chấp nhận các tính năng chưa hoàn thiện, vậy tại sao chúng ta phải làm như vậy trong code mà chúng ta support. Hãy cùng giải thích hành vi kì lạ này.

Hãy tưởng tượng bạn đang đọc code để cố gắng sửa lỗi hoặc thêm tính năng. Bạn làm gì khi gặp phải các đoạn code được comment như ví dụ trên? Nó làm gián đoạn hoàn toàn luồng đọc code của bạn. Các đoạn code được chú thích này là Zombie Code.

Tại sao lại gọi nó là Zombie Code? Vì, zombie thực sự không chết. Như phim kinh dị mà chúng ta hay xem, những thây ma đó dường như đã chết nhưng chúng vẫn còn sống đủ để ám ảnh chúng ta. Tương tự như vậy, Zombie Code nằm giữa ranh giới giữa sống và chết, chỉ chờ cơ hội để phá hỏng cuộc sống của chúng ta. Code được comment vẫn còn tồn tại vì nó nằm trong cơ sở code hiện tại. Các lập trình viên tình cờ tìm thấy nó khi tìm kiếm từ khóa và tương tác với nó trong quá trình bảo trì và tái cấu trúc. Nhưng, code cũng chết vì nó không được thực thi trên production. Do đó, nó là một Zombie cần được chôn ngay lập tức.

Mã ngày nay không bao giờ thực sự chết. Miễn là bạn có source control thì luôn có một tham chiếu lịch sử giúp bạn quay lại các phiên bản trước đó. (Git, SVN, v.v...)

Có 2 nguyên nhân gốc rễ dẫn đến các vấn đề đang diễn ra của Zombie Code:

a. Risk Aversion

Một số lập trình viên cho rằng việc xóa code là rủi ro. Họ thiếu sức mạnh của niềm tin và ý thức mục đích cần thiết để xóa code không cần thiết nên họ tích trữ nó trong các comment nơi nó có thể ám ảnh vào một ngày khác.

Chắc chắn và dễ hơn nhiều khi lập trình viên không cần phải nghĩ đến việc liệu code có nên nằm trong cơ sở mã hay không, nhưng code phải được xóa thường xuyên vì các lập trình viên giỏi biết rằng code là một gánh nặng. Ít hơn thì tốt hơn.

=> Hãy nhớ rằng code được comment vẫn là code, nhưng đó là code không giải quyết được vấn đề nào. Nó chỉ gây cản trở.

b. Hoarding mentality

Các lập trình viên có thể lập luận rằng họ comment code chỉ để phòng trường hợp nó có thể hữu ích cho ai đó sau này. Nhưng đây là hành vi tích trữ và gây hại cho tất cả chúng ta. Code không còn hữu ích trên production vẫn nằm trong lịch sử kiểm soát mã nguồn. Hãy nhớ rằng với kiểm soát mã nguồn, code đã xóa không chết mà chỉ bị chôn sống.

Việc chú thích code chỉ tạo ra noise cho người đọc và làm cho ứng dụng của chúng ta phải chịu gánh nặng kỹ thuật (technical debt). Code được chú thích cũng vô dụng với ứng dụng như đống rác cũ này đối với căn bếp. Những bao bì và nhãn mác cũ kể lại những việc chúng ta từng làm trong bếp, nhưng lại khiến chúng ta không thể thực hiện mọi việc mới một cách hiệu quả.

✅ Chúng ta phải cố gắng giữ Signal to Noise Ratio càng cao càng tốt. Điều này hỗ trợ cho việc hiểu, tăng tốc độ đọc và giúp bảo vệ chúng ta khỏi việc tạo ra mã lỗi do hiểu lầm.

Zombie Code hoàn toàn trái ngược với sự hiểu biết. Nó làm chậm quá trình đọc và bảo trì vì có ít mã sản xuất thực tế trên màn hình tại bất kỳ thời điểm nào. Nó làm nhiễu sự hình dung của người đọc về code vì không rõ liệu người ta có nên đọc nó hay không. Vì một lý do nào đó, chúng ta thường chấp nhận sự thỏa hiệp này với tư cách là lập trình viên, nhưng chúng ta sẽ không bao giờ chấp nhận sự cẩu thả như vậy trong thực tế.

Zombie Code làm cho câu chuyện trở nên không rõ ràng. Một lập trình viên có nên dành thời gian đọc code đã được chú thích hay không? Code đã được chú thích cũng tạo ra sự mơ hồ về việc code có nên được chú thích hay không. Hãy tưởng tượng bạn là một lập trình viên bảo trì dự án, tình cờ tìm thấy một vùng Zombie Code xung quanh khu vực có báo cáo lỗi. Code được chú thích phải được đọc và hiểu để xác định tác động tiềm tàng của nó. Và code có vô tình được chú thích để thử nghiệm và không bao giờ được ngăn chặn không? Có lẽ người đã chú thích có thể giúp được. Vậy, đó là ai? Một cuộc điều tra sẽ diễn ra.

Và sự mơ hồ bổ sung này cần thời gian để giải quyết và tăng thêm gánh nặng tinh thần cho những gì có thể là một quá trình gỡ lỗi đơn giản. Sự mơ hồ này cũng cản trở việc tái cấu trúc. Hãy nhớ rằng chúng ta nên thường xuyên để lại mã tốt hơn một chút so với lúc mới tạo. Nhưng khi một Class hoặc một method chứa một đoạn Zombie Code, mọi thứ trở nên phức tạp. Trong đầu lập trình viên lúc ấy là những câu hỏi "Nếu tôi cấu trúc lại phần này, tôi có cần xem xét đoạn code được chú thích này không? Liệu nó có sớm được bật lại không? Nó sẽ tương tác với việc triển khai mới của tôi như thế nào?" Và đây là những câu hỏi mà các lập trình viên bảo trì không nên hỏi.

Hơn nữa, các công cụ tái cấu trúc tích hợp sẽ không thực hiện những thay đổi tương ứng với code đã được chú thích. Ví dụ bạn sử dụng Refactor name của IDE và đổi tên 1 variable, thì phần mã trong comment nếu có liên quan tới varriable đó thì nó không bị thay đổi tên theo, bạn hiểu vấn đề rồi chứ. Do đó, khi các method, variable và class được đổi tên và chữ ký được thay đổi, mã được chú thích sẽ tụt hậu. Và khi mã được chú thích được phục hồi, rất có thể ứng dụng thậm chí sẽ không biên dịch được.

Tóm tắt: Zombie Code là một vấn đề vì nó làm giảm khả năng đọc mã của chúng ta. Nó tạo ra sự mơ hồ vì chúng ta cần tự hỏi liệu mã được chú thích có nên được chú thích ngay từ đầu hay không. Điều này cũng cản trở việc tái cấu trúc vì chúng ta phải cân nhắc xem code được chú thích có cần được tái cấu trúc hay không khi chúng ta thay đổi code khác xung quanh nó. Nó cũng thêm noise vào tìm kiếm. Khi chúng ta thực hiện tìm kiếm một từ khóa, Zombie Code sẽ xuất hiện và thực sự nó không nên nằm trong phạm vi đó nữa.

Lưu ý: Tâm lí cho rằng Dù sao thì mã cũng không bị "mất"

Hãy nhớ rằng, với source control, chúng ta luôn có thể quay lại kịp thời để xem các phiên bản trước của code đó.

Sau đây là mental checklist

Harmful Tác hại
Creates ambiguity Tạo ra sự mơ hồ
Reduces readability Giảm khả năng đọc
Add noise to searches Thêm noise vào tìm kiếm
Hinders refactoring Cản trở việc tái cấu trúc
Code isn’t “lost” anyway Dù sao thì mã cũng không bị “mất”

Nếu bạn sắp comment code, hãy tự hỏi mình: Khi nào thì code này sẽ được bỏ chú thích? Nếu không có ngày cụ thể sắp tới thì có khả năng là nó sẽ không bao giờ được bỏ chú thích. Tôi có thể xóa phần này và chỉ cần lấy nó từ source control sau nếu cần không? Đây có phải là công việc chưa hoàn thành cần được khôi phục và xử lý thông qua một nhánh thay thế không? Điều này tránh gây thêm noise và nhầm lẫn cho người khác không? Đây có phải là tính năng cần được bật hoặc tắt thông qua cấu hình không? (Nếu bạn đang comment code để tạm thời tắt một tính năng thì có lẽ cài đặt cấu hình (configuration) sẽ phù hợp hơn). Cuối cùng, tôi đã cấu trúc lại hoàn toàn nhu cầu sử dụng mã này chưa? Nếu không còn cần thiết nữa thì hãy xóa nó đi.

Tóm lại: Sắp comment code? Hãy tự hỏi:

  • Khi nào thì code này sẽ không còn được bình luận nữa?
  • Tôi có thể lấy nó từ source control sau được không?
  • Đây có phải là công việc chưa hoàn thành và cần được xử lý thông qua một nhánh (branch code) khác không?
  • Đây có phải là tính năng cần được enabled/disabled thông qua configuration không?
  • Tôi đã cấu trúc lại nhu cầu sử dụng mã này chưa?

8. Dividers and Brace Trackers

Nếu bạn đã từng duy trì một cơ sở mã bị quá tải bởi các function cực dài thì bạn có thể đã thấy mẹo này. Khi các function quá dài, các lập trình viên thường dùng đến chú thích để chia nhỏ các phần như bạn có thể thấy ở đây.

🔴 Dirty

void MyLongFunction() {
  lots
  of
  code

  // Start Search for available concert tickets

  lots
  of
  concert
  search
  code

  // End of concert ticket search

  lots
  more
  code
}

Nếu function của bạn quá dài đến mức khó điều hướng, hãy cân nhắc việc tái cấu trúc thành một vài function riêng biệt để tăng khả năng đọc và loại bỏ hoàn toàn nhu cầu sử dụng các Divider Comment.

Tương tự như Divider Comment mà chúng ta vừa thảo luận, Brace Tracker Comment được sử dụng để giúp lập trình viên điều hướng các đoạn mã thực sự dài. Dưới đây là ý tưởng đằng sau Comment của Brace Tracker:

Khi phần thân của câu lệnh điều kiện dài đến mức chúng ta không thể đưa hết nó lên màn hình thì khi bạn nhìn thấy dấu ngoặc nhọn đóng, bạn có thể không rõ nó đang đóng cái gì. Vì vậy, các Brace Tracker Comment này nằm ở cuối câu điều kiện và cho chúng ta biết câu lệnh nào đang đóng.

🔴 Dirty

private void AuthenticateUsers() {
  bool validLoginInfo = false;
  // deeply
      // nested
            //code
                if(validLoginInfo) {
                    // Lots
                    // of
                    // code
                    // to
                    // log
                    // user
                    // in
                } // end user login
           // even
     // more code      
}

🟢 Clean

private void AuthenticateUsers() {
    bool validLoginInfo = false;
    // deeply
        // nested
            // code
            if (validLoginInfo) {
                LoginUser();
            }
         // even
     // more code    
}

9. Bloated Header

Hãy xem Bloated Header hào nhoáng của tôi

🔴 Dirty

//***********************************************************
// Filename: Login.cs                                       *
//                                                          *
// Author: B-Dragon                                         *
// Created: 13/02/2025                                      *
// Authencate the user: Login, refresh token, ...           *
//                                                          *
// Summary                                                  *
// This class is used to a user login. It includes features *
// login, refresh token, check password, check username,... *
// **********************************************************

Các công ty lập trình ngày xưa đều yêu cầu một mẫu boilerplate tương tự như trên ở đầu mỗi tệp. Ý tưởng là để thực thi tính nhất quán và đảm bảo chúng tôi có tất cả thông tin cần thiết nếu bug xuất hiện. Nhưng có một số vấn đề với ví dụ cụ thể này.

a. Tránh kết thúc dòng trong các bình luận như thế này. Việc giữ các dấu sao ở đúng vị trí thực sự là một rắc rối. Đủ để một số nhà phát triển có thể quyết định không cập nhật tiêu đề để tránh điều đó.

b. DRY (không lặp lại chính mình). Tên file hoàn toàn rõ ràng khi xem tệp và tác giả cùng ngày tạo phải có thể truy cập được thông qua source control.

c. Thay vì tạo ra phong cách bình luận riêng của mình, hãy tuân theo các quy ước về phong cách ngôn ngữ của bạn.

10. Defect Log

🔴 Dirty

// DEF #123 DA 13/02/2025
// We were not checking for "null" here
if(name != null)
{
    // do something...
}

Bạn có thể tránh sử dụng những comment như thế này để ghi lại (logging defect) các bản sửa lỗi trong code của mình. Thay đổi metadata thuộc về kiểm source control không phải code. Thêm các comment như thế này vào code có thể nhanh chóng khiến việc đọc trở thành một công việc thực sự noise.

Hãy tưởng tượng xem sẽ khó chịu thế nào khi đọc một cuốn sách chứa đầy những ghi chú của tác giả về nơi họ sửa lỗi logic và lỗi đánh máy. Điều đó sẽ khiến việc theo dõi câu chuyện của họ trở nên khó khăn hơn nhiều.

Tóm tắt: Defect Log comments cũng gây ra những vấn đề tương tự.

11. Clean Comments

Vậy clean comment là như thế nào, chúng ta hãy cùng xem qua một số ví dụ về Clean Comment:

a. To Do Comments

Các To Do Comment rất tiện lợi vì chúng cho phép lập trình viên dễ dàng quay lại phần code cụ thể cần chú ý hơn sau này.

Tôi ngần ngại gọi To Do Comments là clean vì chúng chắc chắn có thể bị lạm dụng. Nhưng chúng đủ hữu ích để IDE thậm chí cung cấp một cửa sổ danh sách tác vụ chuyên dụng, nơi bạn có thể thấy bất kỳ comment nào bắt đầu bằng To Do, Hack hoặc Undone.

✅ Sau đây là một số mẹo giúp đảm bảo các comment theo phong cách TODO vẫn hữu ích.

  • Nếu IDE bạn chọn không cung cấp hỗ trợ này, hãy đảm bảo Chuẩn hóa cho nhóm của bạn để mọi người đều hiểu mục đích của những comment này.
  • Hãy cẩn thận với nhiều To Do, thực chất chúng là lời xin lỗi hoặc cảnh báo trá hình. Nếu có thời gian để làm ngay bây giờ, hãy hoàn thành, vì thực tế là những việc làm sau thường hay bị bỏ qua. Tôi đã thấy nhiều code base chứa đầy TODO từ những lập trình viên có thiện chí nhưng chưa bao giờ thực hiện danh sách nhiệm vụ của mình.
  • Tuy nhiên, khi được sử dụng một cách hợp lý, TODO comment có thể là một cách tiện lợi để theo dõi các mục cần chú ý trong thời gian ngắn.

b. Summary Comments

Summary Comment cung cấp cho người đọc nhiều lợi ích tương tự như phần giới thiệu ở đầu sách. Chúng chuẩn bị cho người đọc những gì mong đợi.

Summary Comment hay sẽ mô tả code ở cấp độ cao mà không nhất thiết phải thấy rõ bằng cách chỉ xem xét Class hoặc tên method/function. Chúng thường đặc biệt hữu ích khi cung cấp tổng quan cấp cao về các Class vì tên Class thường không đủ để truyền tải đầy đủ phạm vi và tương tác của các method bên trong. Tuy nhiên, hãy đảm bảo rằng bạn không sử dụng Summary Comment để tăng cường các Class hoặc method được đặt tên kém. Các method nhỏ được đặt tên tốt thường có tính tự ghi chú cao đến mức Summary Comment sẽ trở nên thừa thãi.

Vì vậy, hãy cố gắng truyền đạt ý định của bạn trong code. Nhưng hãy chắc chắn thêm Summary Comment để cung cấp cho người đọc ngữ cảnh cấp cao mà họ cần. Đôi khi chúng ta có dữ liệu quan trọng cần theo dõi nhưng không thể truyền tải trong code. Trong những trường hợp này, comment có thể giúp chúng ta cung cấp tài liệu trực tiếp trong code nơi có nhiều khả năng hữu ích nhất.

🟢 Clean

// Encapsulates logic for notifying the user

// Sending custom newsletter emails

c. Document Comments

Những Document Comment như thế này thường hữu ích khi tương tác với bên thứ ba.

🟢 Clean

// See ticket www.jira.com/BDR-123 for documentation

Cảm ơn bạn đã đọc hết bài viết. Nếu thấy hay hãy để lại mình 1 vote và hẹn gặp lại trong bài viết tiếp theo! 🚀


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí