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
Mục lục
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.
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ư black2, isort 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ư flake8, ESLint,…
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.
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ó TypeScript, JSDoc, 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 comments. Link.
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. ⏎