Nguyen Le PhongNguyen Le Phong

Đọc code khó hơn viết code

Một bài nhìn lại vì sao đọc code thường khó hơn viết code, và cách tracing kiên nhẫn, test, naming, lịch sử decision, cùng những note nhỏ giúp engineer hiểu codebase mà không vội rewrite.

Con trỏ đang đứng trên một function nhìn có vẻ vô hại. Nó nhận user, plan và date. Tên function gợi ý một chuyện, test gợi ý chuyện khác, và một comment từ hai năm trước nhắc tới một enterprise exception không ai còn nhớ. Bạn chưa viết dòng nào, nhưng đã thấy mệt. Đọc code đôi khi giống như bước vào một phòng họp sau khi mọi người đã rời đi, rồi cố suy ra toàn bộ cuộc nói chuyện từ những gì còn lại trên bảng.

Viết code có một sự thoải mái riêng: bạn bắt đầu từ intention của mình. Ngay cả khi vấn đề khó, hình dạng đầu tiên vẫn đến từ đầu bạn. Đọc code thì khác. Intention thuộc về người khác, đôi khi thuộc về nhiều người qua nhiều năm. Bạn không chỉ đọc syntax. Bạn đang đọc decision, deadline, incident, naming habit, constraint cũ, và những compromise âm thầm chưa bao giờ đi vào documentation.

Vì vậy engineer có kinh nghiệm vẫn có thể thấy mình chậm trong một codebase lạ. Không phải vì họ quên cách code. Mà vì hiểu nặng hơn gõ phím. Một feature mới có thể chỉ cần sửa một file, nhưng biết file nào đáng sửa có thể cần trace route, kiểm tra test, đọc database migration, search pull request cũ, và hỏi vì sao một branch kỳ lạ tồn tại. Công việc đó vô hình cho tới khi nó ngăn một edit sai.

Code cũng giấu thời gian. Một conditional có thể trông lạ cho tới khi bạn biết nó được thêm sau một customer incident. Một helper bị duplicate có thể trông cẩu thả cho tới khi bạn biết hai flow đang từ từ tách nhau ra. Một abstraction còn thiếu có thể là lựa chọn khôn ngoan vì team đã thử abstraction đó trước đây và đã trả giá. Không có history, mỗi dòng code mời gọi phán xét. Có history, nhiều dòng trở thành evidence của trade-off.

Thói quen khó nhất là không rewrite quá sớm. Rewrite cho cảm giác nhẹ ngay lập tức vì structure của chính mình dễ hiểu với mình hơn. Nhưng sự nhẹ đó có thể đắt nếu bạn chưa hiểu behavior mà code cũ đang bảo vệ. Legacy code thường mang những business rule nhỏ trong túi. Một branch nhìn thừa có thể đang phục vụ một contract cũ. Một null check kỳ quặc có thể đang ngăn production bug chỉ xuất hiện với dữ liệu import từ một partner.

Test giúp vì nó biến việc đọc thành cuộc trò chuyện với evidence. Một test tốt cho thấy code hứa điều gì, không chỉ cách nó đang implement. Khi thiếu test, một characterization test nhỏ có thể là hành động khiêm tốn. Nó nói rằng: trước khi cải thiện phần này, tôi muốn capture behavior hiện tại. Điều đó không biến code xấu thành code tốt. Nó cho lần thay đổi tiếp theo một mặt sàn an toàn hơn.

Trace một path end to end thường tốt hơn cố hiểu tất cả cùng lúc. Bắt đầu từ một user action, một API request, một event, hoặc một failing test. Đi theo nó xuyên qua codebase. Ghi lại tên function, table, queue và service nó chạm tới. Để ý nơi data đổi shape. Để ý nơi error bị nuốt. Để ý nơi log xuất hiện hoặc biến mất. Codebase bớt đáng sợ khi nó chuyển từ một khu rừng thành một con đường.

Tên gọi quan trọng hơn khi người ta đọc code so với khi một người đang viết một mình. Một cái tên mơ hồ bắt mỗi future reader trả một khoản thuế nhỏ. Một cái tên chính xác có thể thay thế cả một đoạn giải thích. Điều này không có nghĩa mọi function cần tên dài. Nó nghĩa là tên nên mang distinction mà reader cần: draft hay submitted, calculated hay stored, display price hay billing price, tenant permission hay user permission. Tên tốt là một hành động chăm sóc nhỏ cho người đến sau.

Comment cũng vậy. Comment hữu ích hiếm khi giải thích điều code đã nói rõ. Nó giải thích vì sao hình dạng này tồn tại, vì sao alternative hiển nhiên bị tránh, hoặc external constraint nào đang được tôn trọng. Một note ngắn như partner import gửi empty string cho missing date có thể cứu ai đó khỏi việc dọn một dòng code đang âm thầm làm việc thật. Comment tốt làm tương lai bớt phải diễn kịch đoán ý.

Đọc code cũng dễ hơn trong team xem câu hỏi là progress. Khi ai đó hỏi vì sao một module có shape như vậy, câu trả lời không nên là bài kiểm tra họ có đủ senior hay không. Đó là cơ hội khôi phục shared memory. Nhiều codebase trở nên khó không phải vì code quá đặc biệt phức tạp, mà vì context chỉ được phép sống trong đầu một vài người.

Có một kiểu engineering kiên nhẫn nằm ở đây. Trước khi đổi code, hãy học job của nó. Trước khi phán một dòng lạ, hãy tìm áp lực đã tạo ra nó. Trước khi thêm abstraction, hãy hiểu duplication mà nó muốn thay thế. Sự kiên nhẫn này không phải chậm. Nó là cách team tránh biến mỗi cải tiến thành một lớp mơ hồ mới.

Lần tới khi đọc code thấy khó hơn viết code, có thể hãy xem độ khó đó là công việc thật, không phải điểm yếu cá nhân. Trace một path. Thêm một note. Capture một behavior trong test. Hỏi một câu cẩn thận. Sự hiểu biết lớn lên qua nhiều lượt nhỏ, và codebase trở nên tử tế hơn mỗi khi ai đó để lại nhiều context hơn lúc họ bước vào.

Bạn thấy bài viết thế nào?