Có một cuộc khủng hoảng thầm lặng đang xảy ra trong các team engineering hiện nay, và phần lớn các Tech Lead chưa đặt tên cho nó.
Team bạn ship code gấp đôi. Số lượng PR tăng 300%. Nhưng production incidents không giảm — ở nhiều team, số này còn đang tăng. Reviewer kiệt sức, đang flag những lỗi style trong diff do AI sinh ra trong khi bỏ qua quyết định kiến trúc quan trọng bị chôn vùi ba tầng bên dưới.
Vấn đề không phải là chất lượng code. Vấn đề là thời điểm chúng ta đang review.
Diff Đã Là Chỉ Số Trễ (Lagging Indicator)
Code review truyền thống được thiết kế cho thế giới mà việc viết code còn chậm. Một developer dành hàng giờ để tạo ra logic. Khi họ mở PR, code đại diện cho suy nghĩ tốt nhất hiện tại của họ. Review diff có lý — vì diff chính là tư duy đó.
AI thay đổi hoàn toàn phương trình này. Một developer có thể mô tả tính năng cho Claude Code và có 500 dòng implementation trong bốn phút. Diff xuất hiện gần như ngay lập tức sau khi intent được hình thành. “Tư duy” xảy ra trong prompt, không phải trong code.
Khi bạn review diff đó, bạn đang kiểm tra output của một quá trình bạn không tham gia. Bạn kiểm tra xem code được sinh ra có nhất quán nội tại không, có tuân theo convention không, test có cover happy path không. Điều bạn không kiểm tra là liệu intent ban đầu có đúng không.
Đây là upstream blindness. Và đây là nguồn gốc thực sự của các vấn đề chất lượng mà các team đang gặp phải.
Upstream Review Trông Như Thế Nào
Upstream code review dịch chuyển artifact được review từ diff sang intent.
Có ba tầng cần review upstream:
1. Đặc tả vấn đề (chúng ta đang giải quyết cái gì?)
Trước khi bất kỳ AI prompt nào được viết, intent cần phải có thể review được. Đây không phải là quy trình nặng nề — nó có thể là một ghi chú thiết kế hai đoạn trong doc dùng chung, hoặc một comment có cấu trúc trong ticket:
Vấn đề: Người dùng có >1000 đơn hàng gặp timeout trên trang danh sách đơn.
Giả thuyết nguyên nhân gốc: N+1 query trên phần join với order items.
Giải pháp đề xuất: Phân trang ở service layer, thêm eager load cho items.
Ràng buộc: Phải đảm bảo backward compatible với mobile API v2.
Tiêu chí chấp nhận:
- Danh sách đơn hàng tải <500ms cho tài khoản có 10k đơn
- Không thay đổi cấu trúc response của GET /api/v2/orders
Cái này có thể review trong hai phút. Một senior engineer có thể phát hiện giả thuyết sai, ràng buộc bị bỏ qua, tiêu chí chấp nhận bỏ lỡ nhu cầu thực sự của người dùng — trước khi bất kỳ code nào được sinh ra.
2. Quyết định kiến trúc (nó khớp với hệ thống như thế nào?)
Với mọi thay đổi đụng đến service boundary, data model, hay caching strategy, quyết định kiến trúc cần được ghi lại và review độc lập với implementation.
Tôi dùng format Architecture Decision Record nhẹ. Không phải full ADR với status và consequences — chỉ là quyết định và lý do của nó, ba đến năm câu:
## Quyết định: Phân trang orders ở service layer, không phải repository layer
Chúng ta sẽ thêm cursor-based pagination vào `OrderService.listByCustomer()`
thay vì trong repository. Điều này giữ interface của repository sạch và cho
phép service áp dụng business rules (ví dụ: lọc đơn đã huỷ) trước khi đếm
số lượng phân trang.
Phương án thay thế đã xem xét: Phân trang trong repository → bị loại vì buộc
business logic vào data layer để xử lý filter huỷ đơn.
Một Tech Lead có kinh nghiệm review cái này có thể phát hiện lỗi trong ba mươi giây. Lỗi tương tự bị chôn trong 300 dòng code được sinh ra mất mười lăm phút để tìm.
3. Test contract (chúng ta đang đảm bảo hành vi nào?)
Test case được viết trước khi sinh code phục vụ hai mục đích: chúng định nghĩa contract mà AI phải thỏa mãn, và chúng có thể review độc lập với implementation.
Bây giờ tôi thường xuyên yêu cầu developer viết test signatures trước khi prompt để lấy implementation:
describe('OrderService.listByCustomer', () => {
it('trả về kết quả phân trang với cursor cho tài khoản có >100 đơn', ...)
it('giữ cursor nhất quán qua các trang mà không có trùng lặp', ...)
it('lọc đơn huỷ trước khi áp dụng giới hạn phân trang', ...)
it('trả về trang rỗng không có cursor khi hết kết quả', ...)
it('throw AuthorizationError khi customer ID không khớp với caller', ...)
})
Review test contract. Đồng ý về nó. Sau đó để AI sinh cả implementation lẫn phần thân test. Contract mới là công việc trí tuệ thực sự — AI điền phần còn lại.
RFC-First Workflow Trong Thực Tế
Kết hợp ba artifact upstream này thành một workflow “RFC-first” nhẹ sẽ chuyển đổi cách review diễn ra.
Flow trông như thế này:
- Developer mở một RFC nhẹ — đặc tả vấn đề, quyết định kiến trúc, test contract. Việc này mất mười lăm đến ba mươi phút.
- Review async trên RFC — một đến hai reviewer, đồng bộ nếu quyết định phức tạp.
- RFC được chấp thuận — developer (hoặc AI) sinh implementation theo contract đã thống nhất.
- PR được mở — reviewer xác nhận implementation khớp với RFC. Họ không tranh luận lại về thiết kế. Họ kiểm tra execution.
Review PR trở nên nhanh và mang tính cơ học. Vì các câu hỏi khó đã được giải đáp rồi.
Tôi chạy workflow này trên team mình trong ba tháng. Chu kỳ review PR giảm từ trung bình 2.1 ngày xuống còn 0.6 ngày. Incidents production từ lỗi kiến trúc giảm xuống gần bằng không. Bản thân RFC review trung bình mất mười một phút.
Cái Gì Vẫn Thuộc Về Diff Review
Upstream review không loại bỏ diff review. Nó thay đổi những gì bạn tìm kiếm.
Trong diff review theo mô hình này, bạn đang kiểm tra:
- Execution fidelity — implementation có khớp với những gì RFC nói không?
- AI hallucinations — model có giới thiệu thứ gì không có trong spec không? (Điều này phổ biến hơn mọi người nghĩ — AI tự tin thêm hành vi “hữu ích”.)
- Edge case mà test contract bỏ sót — xử lý lỗi, concurrency, resource cleanup.
- Code health — đặt tên, độ phức tạp, dead code.
Bạn không xem xét lại kiến trúc. Bạn không tranh luận về thuật toán. Những quyết định đó nằm ở upstream và đã được thực hiện rồi.
Toolchain Xác Thực Intent
Khi các team trưởng thành trong upstream review, họ bắt đầu tự động hóa việc xác thực intent.
Bước thực tế đầu tiên là PR template liên kết đến upstream RFC:
## Checklist PR
- [ ] Liên kết đến RFC / design note đã được chấp thuận
- [ ] Tất cả tiêu chí chấp nhận trong RFC có test tương ứng
- [ ] Không có tính năng nào được thêm ngoài phạm vi RFC (flag là RFC deviation nếu có)
Các team nâng cao hơn chạy automated checks: diff có chạm vào file nằm ngoài phạm vi RFC không? Coverage test có giảm xuống dưới ngưỡng RFC chỉ định không? API surface có thay đổi theo cách không nhất quán với RFC không?
Những checks này nhẹ để implement và bắt được vấn đề AI overshoot phổ biến nhất — model làm hơi nhiều hơn bạn yêu cầu, và hơi nhiều hơn đôi khi là sai.
Sự Thay Đổi Văn Hóa Cần Thiết
Upstream review đòi hỏi một thay đổi văn hóa khó hơn workflow: senior engineer phải dành thời gian sớm hơn trong chu kỳ.
Trong văn hóa review truyền thống, giá trị của senior engineer nằm ở kiểm tra cuối cùng. Trong văn hóa upstream review, giá trị của họ nằm ở làm rõ ban đầu. Điều này có nghĩa là sẵn sàng cho RFC review, không chỉ PR review. Có nghĩa là viết comment trên đặc tả hai đoạn văn, không phải diff ba trăm dòng.
Với một số senior, điều này có vẻ như ít leverage hơn. Trong thực tế, đây là leverage nhiều hơn đáng kể — việc phát hiện giả thuyết sai trước khi AI sinh ra implementation tiết kiệm thời gian hơn nhiều so với phát hiện nó trong PR.
Sự Thay Đổi Đang Xảy Ra
Upstream code review không phải là ý tưởng mới. Các team engineering tốt luôn cố gắng thống nhất về thiết kế trước khi implementation. Điều mới là sự cấp bách.
Khi implementation mất bốn phút thay vì bốn ngày, khoảng cách giữa “intent đã thống nhất” và “code đang chạy” thu hẹp lại. Mô hình review phải thu hẹp cùng với nó.
Các team thích nghi với điều này — dịch chuyển năng lượng review lên upstream — sẽ ship nhanh hơn, ít surprise production hơn, reviewer ít kiệt sức hơn. Các team tiếp tục review diff theo cách họ vẫn làm sẽ thấy rằng nhiều code hơn có nghĩa là nhiều nhiễu hơn, không phải nhiều tín hiệu hơn.
Diff luôn là chỉ số trễ. Bây giờ độ trễ chỉ còn bốn phút.