
Trong nhiều thập kỷ, chúng ta đều biết “code tốt” trông như thế nào:
-
Test đầy đủ
-
Tài liệu rõ ràng
-
Module nhỏ, phạm vi rõ ràng
-
Static typing
-
Môi trường dev có thể khởi chạy dễ dàng
Những thứ này luôn được xem là “tùy chọn”, và khi thời gian gấp gáp thì những thứ tùy chọn thường bị cắt bỏ đầu tiên.
Nhưng AI agents lại cần chính những thứ “tùy chọn” đó.
Agents không giỏi trong việc tạo ra một mớ hỗn độn rồi dọn dẹp sau đó. Chúng có thể giống như một chiếc robot hút bụi Roomba chạy qua phân chó rồi kéo nó khắp nhà.
Các hàng rào bảo vệ (guardrails) duy nhất là những thứ bạn thiết lập và thực thi.
Nếu bối cảnh làm việc của agent không đủ rõ ràng và guardrails không đủ mạnh, bạn sẽ rơi vào một thế giới đầy đau đớn. Nhưng nếu guardrails vững chắc, LLM có thể thử nghiệm không mệt mỏi cho đến khi con đường duy nhất còn lại là đúng.
Đội của chúng tôi chỉ có 6 người, nhưng đã thực hiện nhiều quyết định khá cụ thể — đôi khi gây tranh cãi — để hỗ trợ các “agent coder”. Hãy nói về một vài điều ít hiển nhiên hơn.
100% Code Coverage

Quy tắc gây tranh cãi nhất của chúng tôi cũng là quy tắc giá trị nhất:
Chúng tôi yêu cầu 100% code coverage.
Hầu hết mọi người đều nghi ngờ khi nghe điều này — cho đến khi họ thử sống với nó một ngày. Đôi khi nó giống như một vũ khí bí mật.
Coverage ở đây không chỉ để ngăn bug, mà để đảm bảo rằng agent đã kiểm tra hành vi của từng dòng code mà nó viết ra.
Hiểu nhầm phổ biến là:
-
100% coverage nghĩa là không có bug
-
hoặc chúng tôi chỉ đang đuổi theo một chỉ số
Không phải vậy.
Tại sao lại là 100%?
-
95% coverage: bạn vẫn phải quyết định dòng nào “đủ quan trọng” để test
-
99.99% coverage: bạn không biết dòng chưa test có phải từ trước hay do feature mới
-
100% coverage: mọi sự mơ hồ biến mất
Nếu có dòng chưa được test, đó là do bạn vừa tạo ra nó.
Báo cáo coverage trở thành một danh sách việc cần làm cho test.

Khi đạt 100% coverage, giá trị của test tăng vọt.
Mỗi khi model thêm hoặc sửa code, chúng tôi bắt nó chứng minh hành vi của dòng code đó.
Không được phép dừng ở:
“Có vẻ đúng.”
Nó phải chứng minh bằng ví dụ thực thi được.
Lợi ích khác:
-
Code không thể chạy được sẽ bị xóa
-
Edge cases được làm rõ
-
Code review dễ hơn vì có ví dụ cụ thể cho từng hành vi
Namespace và cấu trúc thư mục
Cách chính để agent điều hướng codebase là:
-
liệt kê thư mục
-
đọc tên file
-
tìm chuỗi
-
load file vào context
Vì vậy cấu trúc thư mục chính là một interface.
Ví dụ:
./billing/invoices/compute.ts
truyền đạt nhiều thông tin hơn rất nhiều so với:
./utils/helpers.ts
dù code bên trong giống nhau.
Hãy tổ chức file có ý nghĩa.
Ngoài ra:
ưu tiên nhiều file nhỏ, phạm vi rõ ràng.
Vì:
-
Agents thường tóm tắt hoặc cắt bớt file lớn
-
File nhỏ giúp toàn bộ nội dung nằm trong context
Kết quả:
-
Agent làm việc nhanh hơn
-
tránh được nhiều lỗi hiệu năng
Dev environment nhanh, tạm thời và chạy song song
Ngày xưa bạn chỉ có một dev environment.
Bạn sửa code, chạy lệnh, restart server và dần dần tìm ra giải pháp.
Nhưng với agents, bạn giống như người nuôi ong, điều phối nhiều tiến trình cùng lúc.
Bạn cần một tổ ong khỏe mạnh.
Fast
Guardrails phải chạy rất nhanh, vì bạn cần chạy chúng thường xuyên.
Chu trình:
Trong setup của chúng tôi:
Mỗi lần npm test sẽ:
-
tạo database mới
-
chạy migrations
-
chạy toàn bộ test suite
Chúng tôi có 10.000+ assertions chạy trong khoảng 1 phút.
Không có cache thì sẽ mất 20–30 phút.
Nếu agent phải chạy test nhiều lần, điều đó sẽ biến thành hàng giờ chờ đợi.
Ephemeral
Khi quen với agents, bạn sẽ bắt đầu chạy nhiều agent cùng lúc.
Điều đó có nghĩa là bạn sẽ:
-
tạo dev environment mới
-
xóa chúng
-
làm việc với nhiều workspace
Chúng tôi có workflow đơn giản:
Lệnh này:
-
tạo git worktree mới
-
copy config local (.env)
-
cài dependency
-
khởi chạy agent
Agent thậm chí có thể phỏng vấn bạn để viết PRD.
Điều quan trọng là độ trễ.
Nếu mất vài phút và nhiều bước thủ công, bạn sẽ không làm.
Nếu chỉ 1–2 giây, bạn sẽ làm liên tục.
Concurrent
Cuối cùng, mọi environment phải chạy cùng lúc được.
Điều đó có nghĩa:
-
ports
-
database
-
cache
-
background jobs
phải cấu hình được và không xung đột.
Docker giúp một phần, nhưng điều quan trọng là isolation tốt.
Bạn phải có khả năng chạy nhiều môi trường dev hoàn chỉnh trên một máy.
Types từ đầu đến cuối
Bạn nên tự động hóa càng nhiều best practices càng tốt.
Hãy giảm số quyết định mà LLM phải tự đưa ra.
Bắt đầu từ:
-
linter tự động
-
formatter
và để chúng tự sửa code khi agent commit.
Nhưng quan trọng hơn:
hãy dùng ngôn ngữ có type.
Types loại bỏ toàn bộ các trạng thái sai có thể xảy ra.
Và chúng thu hẹp không gian tìm kiếm của model.
TypeScript
Chúng tôi dùng TypeScript rất nhiều.
Nếu thứ gì có thể biểu diễn rõ ràng bằng type, chúng tôi làm.
Chúng tôi cũng đặt tên type có ý nghĩa rõ ràng:
-
UserId -
WorkspaceSlug -
SignedWebhookPayload
Điều này giúp model hiểu ngay dữ liệu đang làm việc là gì.
Tên chung chung như T chỉ hữu ích cho thuật toán generic nhỏ, không tốt cho hệ thống business thực tế.
OpenAPI
Ở tầng API, chúng tôi dùng OpenAPI để tạo client typed.
Frontend và backend luôn đồng ý về cấu trúc dữ liệu.
Postgres
Ở tầng dữ liệu, chúng tôi dùng type system của Postgres.
Khi cần, chúng tôi thêm:
-
constraints
-
checks
-
triggers
Nếu agent cố ghi dữ liệu sai, database sẽ báo lỗi ngay lập tức.
Chúng tôi dùng Kysely để tạo TypeScript client có type.
Code tốt không còn là “tùy chọn”
Agents là những lập trình viên không mệt mỏi và đôi khi rất xuất sắc.
Nhưng hiệu quả của chúng phụ thuộc hoàn toàn vào môi trường bạn đặt chúng vào.
Khi nhận ra điều này, “code tốt” không còn là thứ thừa thãi nữa.
Nó trở thành điều bắt buộc.
Đúng là công việc chuẩn bị ban đầu giống như một loại thuế.
Nhưng đó chính là loại thuế mà chúng ta đã né tránh suốt nhiều năm.
Vì vậy hãy trả nó một cách có chủ đích.
Đưa nó vào roadmap kỹ thuật, nhận sự hỗ trợ từ lãnh đạo engineering, và cuối cùng xây dựng codebase mà bạn luôn mong muốn.