2 điểm bởi GN⁺ 2025-03-12 | 1 bình luận | Chia sẻ qua WhatsApp
  • Khi rà soát một codebase gần đây, tác giả đã có trải nghiệm mệt mỏi về mặt tinh thần dù chất lượng mã nhìn chung vẫn tốt
    • Điều này liên quan nhiều hơn đến khả năng đọc hơn là độ phức tạp của mã
  • Tác giả rút ra 8 mẫu để cải thiện khả năng đọc của mã nguồn

Các thước đo khả năng đọc mã nguồn và các thước đo độ phức tạp thay thế

  • Không tồn tại một thước đo phổ quát và được dùng rộng rãi để đo khả năng đọc của mã nguồn
  • Chủ yếu chỉ có các bài báo học thuật không được dùng trong thực tế hoặc các ý kiến cá nhân
  • Thay vì tạo ra một thước đo mới, tác giả tập trung vào các mẫu trực quan mà ai cũng có thể dễ dàng thảo luận
  • Những điều kiện quan trọng đối với một thước đo độ phức tạp:
    • Phải hoạt động ở mức đoạn mã nguồn hoặc từng hàm riêng lẻ
    • Tập trung vào cách viết mã thay vì độ phức tạp của thuật toán
    • Không tập trung vào các yếu tố phong cách (tên biến, khoảng trắng, thụt lề, v.v.)

Thước đo độ phức tạp Halstead

  • Thước đo độ phức tạp mã nguồn do Maurice Halstead vào thập niên 1970 phát triển
  • Có thể lượng hóa cách viết mã bất kể ngôn ngữ hay nền tảng
  • Tính độ dài, thể tích và độ khó của chương trình dựa trên số lượng toán tử và toán hạng
  • Các giá trị đo chính:
    • Số toán tử duy nhất (n1)
    • Số toán hạng duy nhất (n2)
    • Tổng số toán tử (N1)
    • Tổng số toán hạng (N2)
  • Càng dùng nhiều toán tử và toán hạng thì độ phức tạp của mã càng tăng
  • Vì định nghĩa toán tử và toán hạng không phải lúc nào cũng rõ ràng trong mọi ngôn ngữ, việc dùng công cụ nhất quán là rất quan trọng

Những insight rút ra từ độ phức tạp Halstead

  • Các hàm ngắn và có ít biến thường dễ đọc hơn
  • Hạn chế tối đa việc dùng các toán tử theo từng ngôn ngữ hoặc cú pháp rút gọn (syntactic sugar)
  • Việc dùng chain trong lập trình hàm (map/reduce/filter v.v.) nếu quá dài sẽ làm giảm khả năng đọc

Độ phức tạp nhận thức (Cognitive Complexity)

  • Thước đo độ phức tạp do SonarSource phát triển
  • Là nỗ lực nhằm đo chính xác hơn mức độ khó khi đọc mã
  • Ba nguyên tắc chính:
    1. Cú pháp rút gọn (shorthand constructs) giúp giảm độ khó khi đọc
    2. Sự đứt gãy của luồng phi tuyến làm tăng độ khó
    3. Luồng điều khiển lồng nhau làm tăng độ khó

Những insight rút ra từ độ phức tạp nhận thức

  • Cú pháp rút gọn tuy ngắn gọn nhưng tiềm ẩn nguy cơ gây bug
  • Câu lệnh điều kiện và toán tử logic nếu dùng quá mức sẽ làm giảm khả năng đọc
  • Xử lý ngoại lệ là một trong những nguyên nhân chính làm tăng độ phức tạp của mã
  • goto nhìn chung nên tránh, nhưng trong một số tình huống cụ thể có thể hữu ích
  • Cấu trúc điều khiển lồng nhau nên được giảm bớt nếu có thể

Hình dạng, mẫu và biến trong hàm

  • “Hình dạng” trực quan của hàm đóng vai trò quan trọng đối với khả năng đọc của mã
  • Ba nguyên tắc giúp tăng khả năng đọc:
    1. Dùng tên biến rõ ràng và cụ thể
    • Tránh che khuất biến (shadowing)
    • Dùng các tên dễ phân biệt bằng mắt (tránh các tên giống nhau như i, j)
    1. Rút ngắn vòng đời (liveness) của biến
    • Phạm vi sử dụng của biến càng ngắn càng tốt
    • Các biến tồn tại lâu và kéo dài qua ranh giới hàm sẽ làm tăng độ phức tạp
    1. Tái sử dụng các mẫu mã quen thuộc
    • Duy trì các mẫu mã nhất quán sẽ cải thiện khả năng đọc
    • Ưu tiên dùng các mẫu quen thuộc sẵn có hơn là các cách tiếp cận mới

8 mẫu giúp cải thiện khả năng đọc của mã nguồn

  1. Giảm số dòng/toán tử/toán hạng – Hàm nhỏ và ít biến giúp tăng khả năng đọc
  2. Tránh cách tiếp cận mới lạ – Giữ các mẫu quen thuộc trong codebase
  3. Nhóm lại – Tách các chain hàm dài, iterator v.v. thành các hàm phụ trợ
  4. Đơn giản hóa điều kiện – Giữ câu điều kiện ngắn và giảm tối đa việc trộn nhiều toán tử logic
  5. Giảm thiểu goto – Nếu cần thì chỉ dùng hạn chế trong xử lý lỗi
  6. Giảm lồng nhau – Rút gọn logic lồng nhau, nếu cần thì tách thành hàm
  7. Dùng tên biến rõ ràng – Dùng tên biến cụ thể và không trùng lặp
  8. Rút ngắn vòng đời của biến – Giữ chúng ngắn trong hàm và không để vượt qua ranh giới hàm

Kết luận

  • Khả năng đọc của mã là một yếu tố quan trọng của chất lượng mã
  • Halstead và Cognitive Complexity có thể giúp lượng hóa các vấn đề về khả năng đọc và gợi ý hướng cải thiện
  • Viết mã ngắn gọn và rõ ràng sẽ giúp bảo trì dễ hơn và giảm khả năng phát sinh bug
  • Cách viết mã tốt nhất là ưu tiên sự đơn giản, nhất quán và rõ ràng

1 bình luận

 
GN⁺ 2025-03-12
Ý kiến trên Hacker News
  • Việc nối các cấu trúc lập trình hàm như map, reduce, filter thì ngắn gọn, nhưng các chuỗi dài có xu hướng làm giảm khả năng đọc

    • Đây không phải là điều bài viết ngụ ý
    • Có cảm giác như một lời phàn nàn quen thuộc kiểu cho rằng thứ gì đó tệ chỉ vì không quen
    • Chỉ cần quen một chút thì nó dễ đọc và dễ viết hơn các cách khác
    • Điều quan trọng là phải học những điều cơ bản của lập trình hàm
    • Không cần phải giải thích Monad, nhưng cũng nên đủ quen để không công kích map và filter một cách vô cớ
  • Một khía cạnh quan trọng của mã nguồn tốt mang tính định tính và tính văn chương

    • Điều này có thể khiến các lập trình viên và học giả có tư duy toán học cảm thấy không thoải mái
    • Tôi thích Dostoevsky và Wodehouse, nhưng văn của họ rất khác nhau
    • Cần thời gian để hiểu được phong cách của một codebase
  • Vấn đề gây mệt mỏi nhất khi đọc code là tính khả biến

    • Khả năng chỉ "cố định" một biến đúng một lần là một món quà lớn
    • Quá trình hiểu một method nên tăng đơn điệu từ 0% lên 100%
    • Lý do GOTOs có hại là vì khó biết trạng thái của các biến khả biến
  • Hàm nhỏ và ít biến nhìn chung là dễ đọc hơn

    • Trọng tâm vào "khả năng đọc" đang bị lệch về mức độ dễ đọc vi mô
    • Điều này khiến code bị chia vụn quá mức
    • Các ngôn ngữ họ APL nằm ở cực đối diện
  • TypeScript khiến code khó đọc hơn

    • Nếu mô hình dữ liệu được giữ ở mức "nguyên tử" thì không sao
    • Nếu phụ thuộc vào suy luận kiểu thì khó lần theo các field về vị trí ban đầu của chúng
  • Hàm getOddness4 tạo ra sự bất đối xứng

    • Hàm getOddness2 đưa ra lựa chọn đối xứng
  • Bài viết thú vị nhưng chưa thật sự thỏa đáng

    • Không đồng ý với quan điểm nên tránh dùng các toán tử hoặc cú pháp đường mật đặc thù của ngôn ngữ
    • Các cấu trúc như map, reduce, filter nếu dùng tốt có thể thay thế các toán tử khác và giảm "độ ồn"
  • Nỗ lực định nghĩa khả năng đọc là điều đáng khen

    • Với nhiều người, có thể tìm ra các chiều cạnh thực sự của khả năng đọc thông qua kiểm thử
  • Độ phức tạp của code được biểu diễn bằng kích thước của cây cú pháp

    • Việc giảm độ phức tạp cục bộ không ảnh hưởng nhiều đến tổng thể
  • Với chuỗi hàm dài hoặc callback, tốt hơn nên chia thành các nhóm nhỏ và dùng các biến được đặt tên rõ ràng

    • Cả hai phiên bản đều giống nhau về mặt hiệu quả
    • Khác biệt nằm ở trình biên dịch