Readable Code

Tác giả: Minh (Software Engineer, CAI) & Hương (TPM, CAI)

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

— John H. Woods1

1. Readable code là gì?

Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code… [Therefore,] making it easy to read makes it easier to write.

— Robert C. Martin

Khi viết phần mềm, cho dù bạn đang phát triển bất kì một loại ứng dụng nào, ví dụ như web application, desktop application, hay là command-line tool, những dòng code của bạn luôn phải dựa trên yêu cầu từ các stakeholders (ở đây là khách hàng, người quản lý dự án,…). Có khả năng rất cao là trong tương lai, những yêu cầu này sẽ được thay đổi, bỏ đi, hoặc thêm mới. Ngay cả đối với những dự án phần mềm được phát triển một lần, yêu cầu từ phía khách hàng cũng hoàn toàn có thể thay đổi trong quá trình phát triển sản phẩm. Ngoài ra, dù là với sản phầm phần mềm nào, code cũng sẽ cần phải được cải tiến để sửa lỗi và cải thiện hiệu năng.

Như vậy, chỉnh sửa code là một phần không thể thiếu trong việc phát triển phần mềm. Để chỉnh sửa được những đoạn code cũ, trước hết, ta cần phải hiểu được đoạn code cũ đó hoạt động như thế nào. Nếu như chúng ta viết code quá khó đọc, thì người đằng sau sẽ mất nhiều công sức đọc hiểu, và như thế, dù không cố ý, chính chúng ta sẽ làm chậm quá trình sửa code cũ và viết code mới. Do vậy, làm cho code dễ đọc cũng là làm cho code dễ viết hơn.

Sau khi hiểu được nguyên nhân vì sao nên viết code dễ đọc, câu hỏi tiếp theo là làm thế nào để viết code dễ đọc hơn? Phần tiếp theo của bài viết sẽ liệt kê ra các lưu ý mà team Engineers của Got It đã và đang sử dụng để cải thiện code readability của team.

readable code là gì
Source

2. Làm thế nào để viết code dễ đọc?

Code should be written to minimize the time it would take for someone else to understand it.

— Dustin Boswell, Trevor Foucher [1]

Quá trình đọc code có nhiều điểm tương đồng với quá trình hệ điều hành thực thi một chương trình. Tại mọi thời điểm, bạn luôn phải ghi nhớ một lượng thông tin nhất định về đoạn code đang đọc (ví dụ như logic của một block/function/module,…) tương tự như memory của một process. Ngoài ra, việc chuyển đổi qua lại giữa các function/module/… có nét tương đồng với việc hệ điều hành phải switch context giữa các process/thread, và context switching là một tác vụ tốn nhiều tài nguyên của hệ thống. Như vậy, viết code dễ đọc cũng giống như việc tối ưu “memory usage” và hạn chế “context switching” trong process “đọc code”.

Ở phần này, chúng ta sẽ cùng tìm hiểu một số hướng tiếp cận để viết code dễ đọc. Những hướng dẫn này đều dựa vào nhận xét được đưa ra phía trên. Tuy rằng đây không phải là một danh sách hoàn chỉnh, nhưng chúng sẽ giúp bạn có một hình dung ban đầu về code readability. Từ đó, bạn có thể tiếp tục mở rộng ra những kĩ năng khác phức tạp hơn như là cải thiện logic code, hoặc cách tổ chức code của bản thân.

2.1. Style guide

Bước đầu tiên để viết code dễ đọc là làm theo một hướng dẫn chuẩn chỉnh về code style. Nhưng tại sao lại cần có một hướng dẫn về code style? Và tại sao bạn lại cần tuân thủ theo hướng dẫn?

Giả sử rằng bạn ở trong một team lớn, mỗi người lại có một sở thích format code khác nhau, dẫn đến việc khi đọc code, bạn phải làm quen với cách định dạng khác nhau của mỗi người. Điều này sẽ khiến bạn phải thay đổi ngữ cảnh liên tục và làm giảm hiệu suất code.

Bởi vậy, thay vì phải tranh cãi về việc khi nào thì nên xuống dòng, đặt dấu đóng mở ngoặc ở đâu,… chúng ta có thể chọn lấy một code style guide và tập trung vào logic của code. Không phải ngẫu nhiên mà những ngôn ngữ mới như Go hoặc Rust có tích hợp sẵn công cụ để format code (gofmt và rustfmt). Những ngôn ngữ khác, nếu như không được tích hợp sẵn như Go và Rust, thì cũng đều có các công cụ tương tự được phát triển bởi bên thứ ba như black2isort cho Python và prettier cho JavaScript, HTML, CSS,… Ngoài ra, chúng ta còn có những công cụ hỗ trợ linting, static analysis như flake8ESLint,…

2.2. Viết function nhỏ, tập trung vào một tính năng

Do One Thing and Do It Well

— Unix philosophy

Đã bao giờ bạn gặp phải những function to đến mức, khi bạn đọc đến cuối thì bạn lại quên mất logic ở đầu? Bản thân người viết đã từng gặp một vài function như vậy, và đó là những trải nghiệm đọc code không hề dễ chịu chút nào. Những function quá to sẽ khiến cho người đọc không thể tập trung vào logic của toàn bộ function, bởi vì có quá nhiều thứ cần phải ghi nhớ trong quá trình đọc hiểu, trong khi lượng thông tin mà chúng ta có thể nắm bắt được tại một thời điểm là hữu hạn. Chia nhỏ function/method giúp giảm tải nhận thức, dẫn đến việc tăng khả năng tập trung của người đọc.

Ngoài ra, function nhỏ cũng dễ thay đổi hơn so với function lớn. Function nhỏ thông thường sẽ có ít logic phức tạp, nên dễ hiểu và dễ sửa hơn. Độ phức tạp về mặt logic của một function có thể được đo bằng Cyclomatic complexity. Ví dụ, function sau có độ phức tạp là 2:

def do_something(items):
    for item in items:
        do_something_else(item)

Và function sau có độ phức tạp 3:

def do_something(items):
    for item in items:
        if should_do_something_else(item):
            do_something_else(item)

Mỗi khi thêm một đoạn if ... else ..., độ phức tạp logic sẽ tăng thêm 1.

Dựa theo thang đo này, các function nên có độ phức tạp logic tối đa là 10, nếu vượt quá thì nên được chia thành các function nhỏ hơn. Như vậy, chúng ta đã có một tiêu chí rất rõ ràng và cụ thể về việc khi nào thì nên chia nhỏ function. Trên thực tế, độ phức tạp logic của một function nên dựa theo biểu đồ sau: Hầu hết các function có độ phức tạp ≤ 6, cẩn thận khi độ phức tạp lên đến khoảng 7-8, và không bao giờ nên vượt quá 10.

Độ phức tạp của các function

Việc viết function nhỏ có mối quan hệ mật thiết với triết lý của Unix khi được áp dụng cho function. Một function quá to khi nó chứa nhiều logic và phải đảm nhiệm quá nhiều tính năng. Do đó trong trường hợp này, nên chia nhỏ ra để đảm bảo rằng mỗi function chỉ tập trung vào một nhiệm vụ duy nhất, và làm tốt nhiệm vụ của nó.

2.3. Đặt tên hợp lý

There are only two hard things in Computer Science: cache invalidation and naming things.

— Phil Karlton

Đặt tên variable, function, class,… luôn là một vấn đề nan giải. Tuy nhiên, có những quy tắc nhất định giúp cho việc đặt tên dễ dàng, nhất quán hơn, góp phần giúp code viết ra rõ ràng và dễ đọc hơn. Những quy tắc sau được trích ra từ 2 cuốn sách: The Art of Readable Code [1] và Clean Code [2].

2.3.1. Dùng các tiền tố thích hợp để phân loại function

Dùng tiền tố get và compute để phân biệt một hàm có thực hiện tính toán phức tạp hay chỉ là truy cập vào những attribute đơn giản. Ví dụ, hàm get_size nên có độ phức tạp O(1). Nếu chúng ta cần phải lặp qua tất cả các phần tử để đếm số lượng của một tập hợp, tên hàm nên được đặt là compute_size, hoặc count_elements để thể hiện rõ rằng chúng ta cần thực hiện tính toán trong hàm. Làm như vậy sẽ giúp tránh sử dụng những hàm phức tạp ở trong vòng lặp, giúp giảm độ phức tạp về mặt thời gian.

2.3.2. Hạn chế thêm thông tin về type vào tên biến

Trong phần lớn trường hợp, thông tin này không cần thiết. Hầu hết các ngôn ngữ hiện đại đều có type system được phát triển tốt, ngoài ra, các IDE cũng có những tính năng để giúp lập trình viên. Python đã có hỗ trợ typing annotation từ phiên bản 3.6, cùng với sự hỗ trợ của mypy. Với JavaScript, ta có TypeScriptJSDoc, và Flow. Do đó chúng ta hoàn toàn không cần phải đặt tên biến là members_list trong khi có thể kiểm tra được type của nó là List[Member], chỉ cần tên members đã đủ để thể hiện rằng nó đại diện cho một nhóm các member.

2.3.3. Dùng danh từ để đặt tên class/object, dùng động từ để đặt tên hàm

def unix_datetime_getter():
    return int(datetime.utcnow().timestamp())

Tên hàm nên được sửa thành get_unix_datetime, hoặc chính xác hơn, get_unix_timestamp.

2.3.4. Dùng thể khẳng định để đặt tên biến boolean

Quy tắc này sẽ giúp tránh những tình huống như sau:

is_purchase_disallowed: bool = True
if not is_purchase_disallowed:
		purchase_package()

Ở dòng 2, chúng ta có thể phủ định kép, tương đối khó đọc. Đoạn code này có thể sửa lại thành:

is_purchase_allowed: bool = False
if is_purchase_allowed:
		purchase_package()

2.4. Comment

Code never lies. Comments sometimes do.

— Ron Jeffries

Viết comment là một cách thường được dùng để giải thích thêm về những đoạn code khó. Nhưng liệu bạn đã bao giờ tự hỏi rằng mình đã viết comment hợp lý hay chưa? Hay comment mình viết ra có thực sự làm cho code dễ đọc hơn?

Giống như câu trích dẫn ở đầu mục, “code không nói dối, nhưng comment thì thỉnh thoảng có”, người viết đã đọc được một comment xem thêm dòng 127, nhưng thực ra thì phải xem tận dòng… 255, vì đã có thêm nhiều đoạn code được thêm vào trong file kể từ lúc dòng comment trên được viết ra. Hoặc là có comment như sau, xóa tin nhắn từ agent, nhưng code thì ngược lại: chỉ giữ lại tin nhắn từ agent.

# Remove agent messages
filtered_messages = [
    message
    for message in messages
    if message.author.role == "agent"
]

Nhiều người cho rằng nếu bạn viết code đủ tốt thì sẽ hoàn toàn không cần đến comment. Tuy nhiên, khẳng định đó có phần hơi cực đoan, comment vẫn có những tác dụng nhất định trong việc viết code. Salvatore Sanfilippo, tác giả của Redis3, có một bài blog rất hay về comment [3]. Trong đó, anh chia comment ra thành 9 loại và phân tích từng loại một để chỉ ra điểm tốt/xấu.

Làm thế nào để viết comment cho hợp lý là một câu hỏi khó. Cả hai quyển sách The Art of Readable Code [1] và Clean Code [2] đều dành ra một chương để nói về comment và những lời khuyên về việc viết comment một cách hiệu quả. Trong đó, có một ý tóm tắt rất quan trọng: Comment nên có tỉ lệ thông tin cao, và không được chứa bất kì yếu tố gây nhiễu nào.

3. Tổng kết

Trong bài viết có đề cập đến hai cuốn sách Clean Code [2] và The Art of Readable Code [1]. Cuốn sách thứ nhất có phần nổi tiếng, được nhiều người biết đến hơn. Tuy nhiên, quyển The Art of Readable Code có nội dung rất ngắn gọn, súc tích, và cũng dễ đọc hơn quyển Clean Code theo ý kiến cá nhân của người viết. Cả hai đều là những quyển sách rất đáng đọc về việc viết code “sạch đẹp”.

Ngoài những tài liệu đã được trích dẫn, bạn đọc có thể tham khảo thêm những ví dụ cụ thể về cách viết code dễ đọc ở hai GitHub repository sau, dành cho Python và JavaScript:

Mong rằng những chia sẻ trên đây sẽ phần nào giúp bạn hiểu được tầm quan trọng của readable code, cũng như cách để cải thiện code readability của bản thân. Nếu bạn còn có tip khác ngoài những gì được liệt kê trong bài viết thì hãy chia sẻ cùng mọi người bằng cách comment dưới bài viết. Happy Coding!

Tài liệu tham khảo

[1] Dustin Boswell and Trevor Foucher, The Art of Readable Code. O’Reilly Media, Inc., 2012.

[2] Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship. Pearson Education, Inc., 2009.

[3] Salvatore Sanfilippo, Writing system software: code commentsLink.

Footnote

1. Nguồn trích dẫn

2. black ban đầu được phát triển bởi Łukasz Langa, core developer của Python, sau này được chuyển giao lại cho Python Software Foundation.

3. Có thể bạn chưa biết: Redis là viết tắt của REmote DIctionary Server.

Nếu bạn quan tâm, hãy xem các vị trí đang tuyển dụng của Got It tại: bit.ly/gotit-hanoi và đọc thêm về quy trình tuyển dụng tại đây.

https://d1iv5z3ivlqga1.cloudfront.net/wp-content/uploads/2021/04/29235048/1_QAG9RXQyyMAY7i9OYo84FA.png
Got It Vietnam
November 11, 2021
Share this post to:
Tags:
0 Comments
Inline Feedbacks
View all comments
Các bài viết liên quan
Hướng dẫn cách tạo branch trong Git

Hướng dẫn cách tạo branch trong Git

Làm việc trực tiếp trong branch (nhánh) chính của một repository trên GitHub sẽ vô cùng nguy hiểm. Bởi lẽ, bạn sẽ có nguy cơ đưa những dòng code lỗi (bug) vào project đó mà không thông qua việc review code. Vì vậy, để tránh điều này xảy ra, bạn nên tạo một branch mới […]
Readable Code

Readable Code

Tác giả: Minh (Software Engineer, CAI) & Hương (TPM, CAI) Mục lục1. Readable code là gì?2. Làm thế nào để viết code dễ đọc?2.1. Style guide2.2. Viết function nhỏ, tập trung vào một tính năng2.3. Đặt tên hợp lý2.3.1. Dùng các tiền tố thích hợp để phân loại function2.3.2. Hạn chế thêm thông tin về […]
Tìm hiểu Tuple trong Python, phân biệt Tuple và List

Tìm hiểu Tuple trong Python, phân biệt Tuple và List

Ở bài viết này, chúng ta sẽ cùng đi tìm hiểu về Tuple trong Python, cách sử dụng chúng như thế nào, và sự khác biệt giữa Tuple và List là gì? Tất cả những nội dung trong bài đọc sẽ đều có ví dụ minh hoạ cụ thể, hi vọng các bạn đọc có […]
Git fetch là gì? Phân biệt git fetch và git pull

Git fetch là gì? Phân biệt git fetch và git pull

Rất nhiều các bạn đang làm việc trong lĩnh vực IT thắc mắc về cách phân biệt giữa git fetch và git pull. Vậy git fetch là gì? Câu lệnh này hoạt động như thế nào? Và đâu là sự khác biệt giữa hai lệnh git fetch và git pull? Hãy cùng Got It tìm […]
Anaconda là gì? Tìm hiểu nền tảng Khoa học dữ liệu phổ biến nhất

Anaconda là gì? Tìm hiểu nền tảng Khoa học dữ liệu phổ biến nhất

Để có thể tạo nên một ứng dụng của riêng mình, điều quan trọng nhất đó là phải thiết lập môi trường làm việc đúng cách. Vì vậy, bạn cần các công cụ để xử lý dữ liệu, xây dựng các mô hình và biểu diễn trên đồ thị. Việc sử dụng nhiều công cụ […]
Tổng quan về vòng lặp trong Python

Tổng quan về vòng lặp trong Python

Có bao giờ, bạn phải type đi type lại một đoạn code nào đó và cảm thấy vô cùng mất thời gian với việc làm này không? Trong thế giới lập trình có một cách để bạn làm việc này nhanh và hiệu quả hơn. Đó chính là Vòng lặp. Dưới đây, Got It sẽ […]