Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 36 — Code Review: Process và Culture

Scaling Teams and Processes Bài 36/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa gia nhập một startup fintech ở TP.HCM với 40 kỹ sư. Ngày đầu tiên, bạn mở một Pull Request (PR — yêu cầu gộp code) nhỏ để sửa lỗi hiển thị số dư. Ba ngày trôi qua, PR vẫn nằm im. Đến ngày thứ tư, một senior để lại đúng một dòng: "LGTM" (Looks Good To Me — trông ổn đấy). Không ai đọc kỹ, và tuần sau lỗi đó vẫn quay lại production. Ở một đội khác, PR của bạn nhận về 47 comment trong đó 30 comment cãi nhau về việc đặt dấu ngoặc ở đâu, còn cái bug logic thật sự thì không ai thấy.

Cả hai đội trên đều đang làm code review, nhưng cả hai đều thất bại — một bên hình thức, một bên nhiễu loạn. Code review là một trong những "process" (quy trình) có đòn bẩy lớn nhất khi team scale, vì nó vừa là cửa chắn chất lượng (quality gate), vừa là kênh lan truyền kiến thức và văn hóa. Khi team có 5 người, ai cũng biết code của nhau. Khi team lên 50 người, code review chính là cơ chế duy nhất giúp một người hiểu được thứ mà 49 người còn lại đang xây.

Bài này không dạy bạn cú pháp Git hay cách bấm nút "Approve". Bài này nói về việc thiết kế code review như một process có thể mở rộng và nuôi dưỡng một culture (văn hóa) khiến người ta muốn review tử tế thay vì né tránh hoặc gây hấn.

Khái niệm cốt lõi

Code review giải quyết bốn mục đích, không phải một

Nhiều người nghĩ review chỉ để "bắt bug". Thực ra nó phục vụ bốn mục đích song song, và một review tốt phải cân bằng cả bốn:

1. Bug catching (bắt lỗi). Đây là mục đích hiển nhiên nhất: phát hiện lỗi logic, edge case (trường hợp biên) chưa xử lý, lỗ hổng bảo mật, race condition trước khi code lên production. Nghiên cứu kinh điển của Steve McConnell (tác giả Code Complete) tổng hợp rằng review có thể phát hiện 60–90% lỗi, và chi phí sửa một bug ở giai đoạn review rẻ hơn nhiều lần so với sửa khi đã ra production. Tuy nhiên — và đây là điểm nhiều người bỏ qua — bắt bug KHÔNG phải mục đích quan trọng nhất trong dài hạn.

2. Knowledge sharing (chia sẻ kiến thức). Mỗi PR là một buổi học nhỏ. Người review học được phần code mình chưa động tới, người viết học được cách nghĩ của người khác. Ở team scale, đây là vũ khí chống lại "bus factor" — tức là rủi ro cả hệ thống sập vì chỉ một người hiểu một module quan trọng. Nếu chỉ có duy nhất anh Tuấn hiểu module thanh toán, thì ngày anh Tuấn nghỉ phép hay nghỉ việc là ngày cả team tê liệt.

3. Maintainability check (kiểm tra khả năng bảo trì). Code chạy đúng nhưng khó đọc, khó sửa, đặt tên tối nghĩa thì sáu tháng sau chính bạn cũng không hiểu nổi. Review là lúc hỏi: "Người tiếp theo đọc đoạn này có hiểu không? Có phá vỡ quy ước chung của codebase không?".

4. Mentor junior (kèm cặp người mới). Với junior, comment review là một trong những kênh học nghề hiệu quả nhất, cụ thể và sát thực tế hơn nhiều khóa học lý thuyết. Nhưng nó cũng là con dao hai lưỡi: review sai cách sẽ khiến junior sợ hãi, mất tự tin.

Author và Reviewer là một mối quan hệ, không phải một trận đấu

Sai lầm văn hóa lớn nhất là coi review như quan tòa xử bị cáo. Google trong tài liệu Engineering Practices nội bộ (đã công khai) nhấn mạnh một nguyên tắc: reviewer nên approve khi PR "cải thiện tình trạng chung của codebase", ngay cả khi nó chưa hoàn hảo tuyệt đối. Mục tiêu là tiến bộ liên tục, không phải sự hoàn hảo làm tê liệt.

Ngược lại, tác giả cũng có trách nhiệm: viết PR dễ review, mô tả rõ "làm gì và vì sao", tự review lại code của mình trước khi gửi.

Small PR — nguyên tắc vàng của review ở scale

Đây là best practice quan trọng nhất: giữ PR nhỏ, lý tưởng dưới 400 dòng thay đổi. Vì sao con số này? Nghiên cứu nổi tiếng của SmartBear tại Cisco cho thấy khả năng phát hiện lỗi của reviewer suy giảm mạnh khi PR vượt quá 400 dòng — bộ não con người không thể giữ sự tập trung phê phán quá lâu. Một PR 2000 dòng gần như chắc chắn nhận được "LGTM" hình thức, vì không ai đủ kiên nhẫn đọc hết.

PR nhỏ còn kéo theo chuỗi lợi ích: review nhanh hơn, merge sớm hơn, ít conflict hơn, dễ revert (khôi phục) nếu có sự cố. Nó là nền tảng cho một vòng lặp phát triển lành mạnh.

Tình huống thực tế

Tình huống 1 — Sàn TMĐT và cái PR 3.000 dòng "định mệnh"

Một công ty thương mại điện tử tại Hà Nội (gọi là Shopiverse) có team backend 25 người. Một kỹ sư refactor toàn bộ module giỏ hàng trong một PR duy nhất 3.000 dòng, đụng vào 40 file. Vì áp lực deadline chiến dịch 12/12, tech lead review vội trong 20 phút và approve.

Ba tuần sau, đúng ngày sale lớn, giỏ hàng tính sai giá khi khách áp nhiều mã giảm giá cùng lúc — một edge case bị chôn vùi ở dòng 1.847 mà không ai để ý. Thiệt hại ước tính vài trăm triệu đồng doanh thu và vô số khách bực bội.

Bài học: Không phải reviewer kém, mà PR quá lớn khiến review trở nên bất khả thi. Sau sự cố, Shopiverse đặt quy tắc: PR trên 500 dòng phải được chia nhỏ hoặc phải có lý do giải trình trong mô tả PR. Chỉ số review chất lượng của họ (tỷ lệ bug lọt production từ code đã review) giảm 35% trong quý sau đó.

Tình huống 2 — Startup Grab-style và văn hóa review độc hại

Một startup logistics ở Đông Nam Á có một senior nổi tiếng "review gắt". Anh này thường để lại comment kiểu: "Code này ai viết vậy?", "Đọc lại tài liệu đi rồi hãy gửi PR". Kết quả: hai junior trong đội bắt đầu né việc gửi PR, gộp code vào những nhánh riêng và chỉ merge khi anh senior đi vắng. Chất lượng code tổng thể tệ đi vì review — vốn là công cụ nâng chất — lại bị né tránh.

Engineering Manager phát hiện qua chỉ số: thời gian trung bình một PR được merge tăng vọt, và số PR "âm thầm" self-merge tăng. Chị áp dụng một thay đổi văn hóa đơn giản mượn từ tài liệu Google: mọi comment phê phán phải nhắm vào code, không nhắm vào người; và khuyến khích dùng tiền tố như "nit:" (nitpick — chuyện nhỏ, không bắt buộc sửa) để phân biệt góp ý bắt buộc với góp ý tùy chọn.

Bài học: Review là hành vi con người trước khi là hành vi kỹ thuật. Một reviewer giỏi về mặt kỹ thuật nhưng độc hại về giao tiếp sẽ làm hỏng cả process. Culture ăn đứt process khi hai thứ xung đột.

Tình huống 3 — Đội ngân hàng số áp dụng CODEOWNERS để scale

Một ngân hàng số Việt Nam mở rộng đội kỹ thuật từ 30 lên 120 người trong một năm. Vấn đề nảy sinh: ai review cho ai? PR về module KYC (định danh khách hàng) đôi khi lọt vào tay người không rành nghiệp vụ, dẫn tới approve sai.

Họ triển khai file CODEOWNERS trên GitHub — một cơ chế tự động gán reviewer bắt buộc theo từng thư mục. Ví dụ, mọi thay đổi trong thư mục payments/ bắt buộc phải có ít nhất một người trong team Payments approve. Kết hợp với quy tắc "hai mắt" (two-pass: mỗi PR đụng vào code nhạy cảm cần hai người duyệt), tỷ lệ lỗi nghiệp vụ lọt qua giảm rõ rệt.

Bài học: Ở scale lớn, bạn không thể trông chờ vào việc "ai rảnh thì review". Cần cơ chế định tuyến (routing) rõ ràng để đúng người review đúng chỗ. Process biến review từ chuyện ngẫu nhiên thành chuyện có hệ thống.

Hướng dẫn từng bước

Dưới đây là quy trình bạn có thể áp dụng cho đội mình, từ phía tác giả PR đến phía reviewer.

Bước 1 — Tác giả chuẩn bị PR tốt. Trước khi gửi, hãy tự review lại diff của chính mình. Viết mô tả PR trả lời ba câu: Thay đổi gì? Vì sao? Cách kiểm chứng (test) thế nào? Đính kèm ảnh chụp màn hình nếu là thay đổi giao diện. Giữ PR nhỏ, tập trung một mục đích duy nhất — đừng gộp "sửa bug" với "đổi format" với "thêm tính năng" vào một PR.

Bước 2 — Định tuyến đúng reviewer. Dùng CODEOWNERS hoặc quy ước rõ ràng để chọn người review có ngữ cảnh phù hợp. Với thay đổi nhạy cảm (bảo mật, thanh toán), bắt buộc reviewer có chuyên môn tương ứng.

Bước 3 — Reviewer đọc theo lớp, từ cao xuống thấp. Đừng lao vào bắt lỗi dấu chấm phẩy ngay. Hãy hỏi theo thứ tự ưu tiên: (1) Cách tiếp cận (design) có đúng không? (2) Logic có đúng không, có bỏ sót edge case không? (3) Có rủi ro bảo mật/hiệu năng không? (4) Có test đủ không? (5) Cuối cùng mới đến khả năng đọc, đặt tên, style.

Bước 4 — Comment rõ ràng, phân loại mức độ. Dùng tiền tố: nit: cho góp ý nhỏ tùy chọn, question: khi bạn chưa hiểu và muốn hỏi, blocking: khi đây là vấn đề bắt buộc sửa trước khi merge. Điều này giúp tác giả biết cái gì cần sửa, cái gì chỉ là gợi ý.

Bước 5 — Đặt kỳ vọng về thời gian phản hồi (SLA). Một quy ước tốt: phản hồi PR trong vòng một ngày làm việc. PR bị treo lâu là kẻ thù của năng suất — nó chặn tác giả và tích lũy conflict.

Bước 6 — Tự động hóa những gì máy làm được. Đừng để con người tranh cãi về style. Cài linter, formatter (như Prettier, Black, gofmt) và CI (kiểm thử tự động) chạy trước khi review. Con người chỉ nên review những thứ máy không làm được: thiết kế, logic, ngữ cảnh nghiệp vụ.

Bước 7 — Kết thúc dứt khoát. Khi các vấn đề blocking đã được giải quyết, approve và merge. Đừng để PR "hoàn hảo hóa" vô tận. Nhớ nguyên tắc: mục tiêu là codebase tốt hơn hôm qua, không phải hoàn hảo tuyệt đối.

Lỗi thường gặp & mẹo

Lỗi 1: Rubber-stamp review (đóng dấu cho có). "LGTM" trong 30 giây mà không thực sự đọc. Đây là review giả. Mẹo: nếu bạn không có thời gian review tử tế, hãy nói thẳng và nhờ người khác, thay vì approve cho xong.

Lỗi 2: Bikeshedding (sa đà chuyện vặt). Cãi nhau 20 comment về việc đặt dấu ngoặc, trong khi bug logic ngồi ngay đó. Mẹo: tự động hóa style bằng linter để loại bỏ hoàn toàn loại tranh cãi này khỏi bàn review.

Lỗi 3: Review nhắm vào người. "Sao bạn lại viết thế này?" nghe như buộc tội. Mẹo: đổi sang "Đoạn này nếu input là null thì xử lý ra sao nhỉ?" — nhắm vào code, đặt câu hỏi thay vì phán xét.

Lỗi 4: PR khổng lồ. Như tình huống Shopiverse. Mẹo: nếu buộc phải làm thay đổi lớn, hãy chia thành chuỗi PR nhỏ (stacked PRs) hoặc tách phần refactor ra khỏi phần thêm tính năng.

Lỗi 5: Bỏ qua "vì sao". Reviewer chỉ đọc code mà không đọc mô tả, dẫn tới hiểu sai ý định. Mẹo: tác giả phải viết mô tả tốt; reviewer phải đọc nó trước khi mở diff.

Mẹo nâng cao: Với junior, hãy áp dụng nguyên tắc "khen cụ thể, sửa cụ thể". Đừng chỉ để lại comment sửa lỗi — khi thấy một đoạn code hay, hãy nói "cách xử lý lỗi ở đây gọn đấy". Điều này biến review thành trải nghiệm nuôi dưỡng chứ không chỉ là kiểm soát. Ngoài ra, với những vấn đề lớn về thiết kế, đôi khi tốt hơn là nhấc điện thoại hoặc họp 15 phút thay vì viết một chuỗi comment dài — giao tiếp đồng bộ giải quyết bất đồng nhanh hơn.

Bài tập thực hành

  • Tự chấm điểm PR gần nhất. Lấy PR cuối cùng bạn gửi. Đếm số dòng thay đổi. Nếu trên 400 dòng, hãy viết ra cách bạn có thể chia nó thành 2–3 PR nhỏ hơn tập trung hơn.
  • Viết lại comment độc hại. Cho ba comment sau, hãy viết lại theo hướng nhắm vào code và mang tính xây dựng: (a) "Code này sai rồi", (b) "Ai dạy bạn viết vậy?", (c) "Đọc lại docs đi". So sánh phiên bản của bạn với cách tiếp cận "đặt câu hỏi thay vì phán xét".
  • Thiết kế review checklist cho team. Soạn một checklist 5–7 mục mà mọi reviewer trong đội bạn nên tự hỏi khi review (gợi ý: có test không? edge case? bảo mật? đặt tên rõ chưa?). Chia sẻ với đội và thu thập phản hồi.
  • Phân loại comment. Trong lần review tiếp theo, hãy thử dùng tiền tố nit:, question:, blocking: cho từng comment. Sau đó tự đánh giá: việc phân loại có giúp tác giả phản hồi nhanh và đúng trọng tâm hơn không?
  • Đo lường. Nếu có quyền truy cập công cụ (GitHub, GitLab), hãy xem thời gian trung bình từ lúc mở PR đến lúc merge trong đội bạn tháng qua. Con số đó nói lên điều gì về sức khỏe process review của đội?

Tóm tắt

Code review ở quy mô nhỏ là chuyện tự nhiên; ở quy mô lớn nó phải trở thành một process được thiết kế cẩn thận và một culture được nuôi dưỡng chủ động. Bốn mục đích của review — bắt bug, chia sẻ kiến thức, kiểm tra khả năng bảo trì, và kèm cặp junior — phải được cân bằng, trong đó chia sẻ kiến thức và văn hóa mới là giá trị lâu dài nhất khi team mở rộng.

Nguyên tắc vàng để review khả thi ở scale là giữ PR nhỏ, dưới 400 dòng: bộ não con người không review nổi những khối code khổng lồ. Về mặt process, hãy định tuyến đúng reviewer (CODEOWNERS), đặt SLA phản hồi, và tự động hóa mọi thứ máy làm được để con người tập trung vào thiết kế và logic. Về mặt culture, hãy nhắm vào code chứ không nhắm vào người, phân loại comment theo mức độ, và nhớ rằng mục tiêu là làm codebase tốt hơn mỗi ngày chứ không phải theo đuổi sự hoàn hảo gây tê liệt.

Ba tình huống — PR 3.000 dòng gây sự cố sale, reviewer độc hại khiến junior né tránh, và ngân hàng số dùng CODEOWNERS để scale — đều cho thấy cùng một bài học: review thất bại không phải vì kỹ sư kém, mà vì thiếu một process và culture đủ tốt để review phát huy sức mạnh. Xây được hai thứ đó, bạn biến code review từ một cửa ải phiền toái thành một trong những đòn bẩy chất lượng và học hỏi mạnh nhất của cả tổ chức.