Mở đầu — vì sao bài này quan trọng
Nếu bạn đã từng nhận một comment trong pull request kiểu "cái này sai rồi" mà không có lời giải thích nào, bạn hiểu cảm giác đó tệ thế nào. Ngược lại, nếu bạn từng để lại một comment gay gắt rồi sau đó thấy đồng nghiệp im lặng, làm việc dè chừng với bạn suốt hai tuần, bạn cũng hiểu cái giá của việc review thiếu tinh tế.
Code review không chỉ là kỹ thuật — nó là một trong những hoạt động giao tiếp diễn ra thường xuyên nhất trong đời một kỹ sư. Một engineer trung bình có thể tham gia hàng chục pull request mỗi tuần, và mỗi comment bạn viết đều là một hành động giao tiếp có sức nặng. Nó có thể nâng đỡ đồng nghiệp, dạy họ điều gì đó mới, xây dựng lòng tin — hoặc làm họ mất mặt, thấy bị soi mói, và dần dần né tránh việc để bạn review.
Ở Bài 12, chúng ta đã bàn về code review như một kênh giao tiếp và cách tổ chức luồng review. Bài 13 này đi sâu vào một mặt cụ thể và khó nhất: cách bạn đưa feedback (giving feedback) trong code review. Trọng tâm không phải là "code này đúng hay sai", mà là "làm sao truyền đạt đánh giá của bạn một cách rõ ràng, đúng mức độ nghiêm trọng, và tôn trọng con người phía bên kia màn hình". Đây là kỹ năng phân biệt một reviewer mà người ta sợ với một reviewer mà người ta muốn được review.
Khái niệm cốt lõi
Bốn cấp độ nghiêm trọng của feedback
Vấn đề lớn nhất của feedback trong code review là mọi comment trông như nhau về mặt trọng lượng. Một góp ý "nên đổi tên biến cho dễ đọc" và một cảnh báo "chỗ này sẽ mất dữ liệu người dùng" đều hiển thị dưới dạng cùng một khung comment màu xám. Người viết code không biết cái nào bắt buộc phải sửa, cái nào chỉ là ý kiến. Kết quả: hoặc họ sửa hết mọi thứ (lãng phí thời gian, PR kéo dài), hoặc họ bỏ qua cả những cái quan trọng.
Giải pháp là phân loại feedback thành bốn cấp độ nghiêm trọng rõ ràng, và gắn nhãn cho mỗi comment:
- Blocker (Chặn merge) — Đây là những vấn đề khiến code không thể merge. Bug rõ ràng, lỗ hổng bảo mật, rủi ro mất dữ liệu (data loss), race condition, code không chạy được. Reviewer phải chặn và người viết phải sửa trước khi tiến tiếp. Không có ngoại lệ.
- Major (Nên sửa trước khi merge) — Vấn đề nghiêm trọng nhưng không phải là thảm họa tức thì: thiếu error handling ở nhánh quan trọng, thiếu test cho logic phức tạp, thiết kế API sẽ khó mở rộng, vi phạm nguyên tắc kiến trúc đã thống nhất. Nên giải quyết trước merge, nhưng có thể thảo luận nếu có lý do chính đáng để hoãn.
- Minor (Nên sửa nhưng không chặn) — Cải thiện chất lượng: đặt tên biến chưa rõ, hàm hơi dài nên tách, thiếu comment giải thích một đoạn tricky. Người viết nên cân nhắc, nhưng reviewer không nên chặn PR vì những điều này.
- Nit / Suggestion (Ý kiến cá nhân) — Sở thích cá nhân, gợi ý phong cách, "nếu là mình thì mình sẽ...". Người viết hoàn toàn tự do bỏ qua. Phải nói rõ đây chỉ là nit để họ không cảm thấy áp lực.
Gắn nhãn tường minh — nguyên tắc "prefix"
Cách thực thi mạnh nhất của hệ thống bốn cấp là dùng tiền tố (prefix) ở đầu mỗi comment. Đây là quy ước phổ biến (nổi tiếng nhất là bộ "conventional comments"):
blocker:hoặc[blocker]— bắt buộc sửa.issue:— vấn đề cần giải quyết.nit:— chuyện nhỏ, tùy bạn.question:— mình chưa hiểu, giải thích giúp.suggestion:— một cách khác, cân nhắc thôi.praise:— khen ngợi, ghi nhận điều làm tốt.
Phân tách con người khỏi code
Nguyên tắc nền tảng: bạn review code, không review con người. Sự khác biệt nằm ở cách diễn đạt.
- "Anh viết hàm này sai logic rồi" → tấn công người viết.
- "Hàm này có vẻ trả về sai giá trị khi input rỗng — em thử case đó chưa?" → nói về code, kèm sự tò mò.
Giải thích "tại sao", không chỉ "cái gì"
Một comment tốt luôn kèm lý do. "Nên dùng const ở đây" là mệnh lệnh. "Nên dùng const ở đây vì biến này không bị gán lại, giúp người đọc sau biết chắc giá trị không đổi" là một bài học. Feedback có "tại sao" giúp người viết học được nguyên tắc và tự áp dụng lần sau, thay vì chỉ sửa máy móc theo yêu cầu.
Tình huống thực tế
Ví dụ 1 — Reviewer khắt khe làm junior mất tự tin (một startup fintech ở TP.HCM)
Tại một startup fintech khoảng 40 người ở Quận 1, có một senior engineer tên Tuấn nổi tiếng "review kỹ". Comment của anh thường ngắn gọn: "Sai", "Không được", "Đã bảo rồi mà", "Đọc lại docs đi". Về mặt kỹ thuật anh gần như luôn đúng. Nhưng dữ liệu nội bộ cho thấy PR nào Tuấn review trung bình mất 4,2 ngày để merge, so với 1,5 ngày của các reviewer khác. Một bạn junior tên Linh thừa nhận trong buổi 1-on-1 rằng cô cố tình tránh assign PR cho Tuấn, và có lần đã ngồi 40 phút chỉ để sửa lại một PR cho "hoàn hảo" trước khi dám gửi cho anh — thời gian đáng lẽ dùng để làm việc khác.
Khi tech lead ngồi lại phân tích, họ nhận ra vấn đề không phải nội dung feedback của Tuấn mà là cách đưa. Anh gộp blocker thật sự với nit về đặt tên vào cùng một giọng điệu gay gắt, khiến người nhận không phân biệt được và luôn ở trạng thái phòng thủ. Team thống nhất áp dụng prefix blocker/issue/nit/question. Sau một tháng, thời gian merge trung bình của PR Tuấn review giảm còn 2 ngày, và điều quan trọng hơn: các junior bắt đầu chủ động nhờ anh review vì giờ họ đọc được đâu là điều thật sự quan trọng.
Bài học: Đúng về kỹ thuật là chưa đủ. Nếu feedback của bạn khiến người ta né tránh, giá trị chuyên môn của bạn không lan tỏa được. Phân loại mức độ nghiêm trọng và làm dịu giọng điệu không phải là "nhẹ tay", mà là làm cho feedback thực sự đến đích.
Ví dụ 2 — Nit vô tận làm nghẽn một PR quan trọng (một công ty product ở Singapore)
Một team backend tại một công ty product SaaS ở Singapore có PR migration cơ sở dữ liệu cần merge gấp trước một đợt release. Reviewer để lại 23 comment. Người viết hoảng loạn ngồi sửa từng cái. Đến comment thứ 18, cả hai mới nhận ra 19 trong số 23 comment chỉ là chuyện phong cách: dấu cách, thứ tự import, một câu "mình thích early return hơn". Trong khi đó, một comment thật sự quan trọng — thiếu index cho một cột được query nhiều — thì bị chôn ở giữa và suýt bị bỏ qua.
Đội này rút ra hai thay đổi. Thứ nhất, họ đưa toàn bộ chuyện format vào linter và formatter tự động (Prettier, ESLint, gofmt) để không ai phải comment tay về style nữa. Thứ hai, họ đặt quy tắc: reviewer phải gắn nit: cho mọi ý kiến phong cách, và người viet được phép merge mà không cần giải quyết bất kỳ nit nào. Comment về thiếu index được nâng lên blocker: và xử lý ngay.
Bài học: Đừng để nit làm loãng blocker. Máy làm được thì để máy làm — con người dành sự chú ý cho những gì máy không làm được: logic, thiết kế, ý định. Và khi bạn phải nêu nit, hãy gắn nhãn rõ để nó không cạnh tranh sự chú ý với vấn đề thật.
Ví dụ 3 — Chuyển "phán xét" thành "câu hỏi" cứu một cuộc tranh cãi
Một team ở Hà Nội có tình huống: reviewer thấy người viết dùng một thư viện mới toanh cho việc parse ngày tháng, trong khi codebase đã có sẵn utility. Bản nháp comment đầu tiên là: "Sao lại thêm dependency mới? Không được tự ý thêm thư viện. Dùng cái có sẵn đi."
Reviewer dừng lại và viết lại: "question: Mình thấy chỗ này thêm thư viện date-fns. Bên utils/date.ts mình có sẵn vài helper — có lý do gì khiến chúng không đủ dùng cho case này không? Nếu có mình muốn hiểu thêm, còn nếu chỉ do chưa biết thì mình nghĩ ta nên tái sử dụng để giảm bundle size."
Hóa ra người viết có lý do: utility cũ xử lý sai múi giờ cho khách hàng ở Nhật. Cuộc trao đổi biến thành một quyết định tốt cho cả team — họ nâng cấp utility cũ thay vì thêm dependency, và ghi lại lý do. Nếu comment ban đầu được gửi đi, nó đã thành một cuộc tranh cãi ai-đúng-ai-sai.
Bài học: Khi bạn không chắc mình đã nắm hết ngữ cảnh (mà bạn hầu như không bao giờ nắm hết), hãy hỏi thay vì phán. Câu hỏi mở ra thông tin; phán xét đóng cửa nó lại.
Hướng dẫn từng bước
Đây là quy trình bạn có thể áp dụng cho mỗi comment bạn định viết:
- Đọc để hiểu ý định trước, phán xét sau. Trước khi gõ comment đầu tiên, đọc toàn bộ PR một lượt để hiểu người viết đang cố làm gì. Nhiều comment "sai" biến mất khi bạn hiểu bức tranh lớn.
- Xác định cấp độ nghiêm trọng. Với mỗi vấn đề, tự hỏi: điều này có chặn merge không (blocker), nên sửa trước merge không (major/issue), nên cải thiện không (minor), hay chỉ là sở thích của mình (nit)? Trung thực với chính mình — phần lớn comment thực chất là minor hoặc nit.
- Gắn prefix. Bắt đầu comment bằng nhãn:
blocker:,issue:,nit:,question:,suggestion:,praise:. Đây là 30 giây tạo ra khác biệt lớn nhất.
- Nói về code, đặt câu hỏi khi phù hợp. Dùng "đoạn code này / hàm này" thay vì "bạn". Khi chưa chắc ngữ cảnh, chuyển phán xét thành câu hỏi.
- Giải thích tại sao, và đề xuất hướng đi. Đừng chỉ nêu vấn đề — nếu được, gợi ý một hướng giải quyết hoặc đưa link tài liệu. "Chỗ này có thể null pointer khi list rỗng; ta có thể thêm guard
if (list.isEmpty()) return."
- Cân bằng bằng lời khen thật. Gắn
praise:cho điều làm tốt. Không phải để "cho có" mà vì việc ghi nhận đúng chỗ dạy người ta điều gì tốt để lặp lại, và làm cho feedback tiêu cực dễ tiếp nhận hơn.
- Biết khi nào nên chuyển sang trao đổi trực tiếp. Nếu một chủ đề đã đi qua 3–4 vòng comment qua lại, đó là tín hiệu nên gọi một cuộc trao đổi nhanh (call hoặc gặp trực tiếp) rồi tóm tắt kết luận lại vào PR. Văn bản không phải lúc nào cũng là kênh tốt nhất cho tranh luận phức tạp.
Lỗi thường gặp & mẹo
Lỗi: "review theo cân nặng". Cảm giác phải để lại thật nhiều comment để chứng tỏ mình review kỹ. Số lượng comment không đo lường chất lượng review. Đôi khi một comment blocker đúng chỗ giá trị hơn 20 nit.
Lỗi: bikeshedding. Dành phần lớn năng lượng tranh cãi những thứ nhỏ và dễ (đặt tên, format) trong khi lướt qua những quyết định kiến trúc khó. Càng dễ hiểu vấn đề, người ta càng thích tranh luận — hãy cảnh giác với xu hướng này ở chính mình.
Lỗi: comment mơ hồ. "Cái này không ổn" không giúp được ai. Luôn cụ thể: ổn ở điểm nào, tại sao, và nên làm gì.
Lỗi: gộp mọi thứ vào một giọng điệu. Blocker nghiêm túc và nit vui vẻ mà cùng một tông giọng lạnh lùng sẽ khiến nit bị hiểu nhầm là nghiêm trọng và người nhận luôn căng thẳng.
Mẹo — quy tắc "nếu là câu hỏi thì hỏi thật". Đừng dùng câu hỏi tu từ để mỉa mai ("Bạn có thật sự nghĩ cái này chạy được không?"). Đó là công kích trá hình. Câu hỏi phải là câu hỏi thật, muốn nghe câu trả lời.
Mẹo — tự động hóa cái tự động hóa được. Format, lint, import order, độ dài dòng: đẩy hết cho linter/formatter và CI. Con người chỉ review cái mà máy không thấy được.
Mẹo — dùng "suggestion block" của GitHub/GitLab. Với sửa nhỏ, dùng tính năng gợi ý code trực tiếp để người viết chấp nhận bằng một cú click, tiết kiệm thời gian cả hai.
Mẹo — tự đọc lại comment trước khi bấm gửi. Đọc như thể bạn là người nhận vào một ngày mệt mỏi. Nếu nó khiến bạn nhói lòng, hãy viết lại.
Bài tập thực hành
- Phân loại và viết lại. Lấy một PR gần đây bạn từng review (hoặc từng nhận). Với mỗi comment, gán một trong bốn nhãn: blocker / issue / minor / nit. Bạn sẽ ngạc nhiên vì tỷ lệ nit cao đến mức nào. Sau đó viết lại 3 comment gay gắt nhất theo hướng: gắn prefix, nói về code, thêm "tại sao".
- Bài tập chuyển phán xét thành câu hỏi. Viết lại 5 comment sau thành dạng câu hỏi hoặc gợi ý tôn trọng:
- Thử thách một tuần. Trong tuần tới, mọi comment code review bạn viết phải bắt đầu bằng một prefix, và mỗi PR bạn review phải có ít nhất một comment
praise:chân thành. Ghi lại xem cách người viết phản hồi có thay đổi không.
- Tự audit. Nhờ một đồng nghiệp bạn tin tưởng đọc lại 10 comment review gần nhất của bạn và trả lời một câu: "Nếu nhận những comment này, bạn cảm thấy thế nào?" Lắng nghe, không biện minh.
Tóm tắt
Đưa feedback trong code review là một kỹ năng giao tiếp có thể luyện được, không phải tính cách bẩm sinh. Những điểm cốt lõi cần nhớ:
- Phân loại theo bốn cấp độ nghiêm trọng: blocker (chặn merge), major/issue (nên sửa trước merge), minor (nên cải thiện), nit (tùy chọn). Đừng để mọi comment trông như nhau.
- Gắn prefix tường minh cho mỗi comment — đây là thay đổi nhỏ nhất tạo tác động lớn nhất.
- Review code, không review con người. Nói về "đoạn code này", giả định thiện chí, và chuyển phán xét thành câu hỏi khi bạn chưa nắm hết ngữ cảnh.
- Luôn giải thích "tại sao" và đề xuất hướng đi để feedback trở thành bài học chứ không phải mệnh lệnh.
- Tự động hóa chuyện format, để con người dành sự chú ý cho logic, thiết kế và ý định.
- Cân bằng bằng lời khen thật và biết khi nào nên rời văn bản để trao đổi trực tiếp.