Feature càng lớn, bạn càng phải chia nhỏ AI generation thành từng bước có thể verify và commit độc lập — nếu không, một lỗi nhỏ ở giữa sẽ kéo sập toàn bộ công sức của cả buổi làm việc.
Vì Sao Big-Bang AI Generation Thất Bại
Có một phản xạ rất tự nhiên khi mới làm việc với AI coding agent (agent lập trình AI): viết một prompt thật chi tiết, mô tả toàn bộ feature từ đầu đến cuối, rồi bấm Enter và chờ agent "generate" ra tất cả — schema, service, API, UI, test — trong một lượt chạy duy nhất. Cách làm này, gọi là "big-bang generation", trông có vẻ hiệu quả trên demo, nhưng trong môi trường production nó thất bại theo những cách rất đặc trưng.
Thứ nhất là vấn đề review. Khi agent trả về một diff (bản khác biệt code) 800-1000 dòng trải dài qua migration, model, service, controller, và test, việc review nghiêm túc gần như bất khả thi. Không ai đủ tập trung để bắt lỗi logic ẩn trong một service method nằm giữa một khối thay đổi khổng lồ. Kết quả thực tế là reviewer chỉ lướt qua, approve cho xong, và bug lọt vào codebase.
Thứ hai là vấn đề debug khi có lỗi. Giả sử sau khi merge, feature notification gửi email trùng lặp cho user. Lỗi này nằm ở đâu? Trong logic dispatch của service layer? Trong transaction ở data layer? Trong retry logic của API layer? Khi tất cả được tạo ra cùng lúc và commit thành một commit duy nhất, bạn mất khả năng bisect (dùng git bisect để nhị phân tìm commit gây lỗi) — vì toàn bộ feature nằm trong đúng một commit, git log không cho bạn manh mối nào về việc phần nào được viết trước, phần nào phụ thuộc vào phần nào.
Thứ ba, và đây là điều nhiều engineer chưa để ý: agent cũng có giới hạn về context window (cửa sổ ngữ cảnh — lượng thông tin agent có thể "nhớ" và xử lý cùng lúc). Khi bạn yêu cầu agent generate toàn bộ feature trong một lần, nó phải giữ trong đầu đồng thời: schema đang thiết kế, service logic đang viết, API contract đang định nghĩa, và convention của codebase. Càng về cuối response, agent càng có xu hướng quên chi tiết đã quyết định ở đầu — dẫn đến naming không nhất quán, service gọi sai field name so với schema vừa tạo, hoặc validation logic ở API layer không khớp với business rule ở service layer. Đây không phải agent "ngu", mà là hệ quả tất yếu của việc nhồi quá nhiều việc vào một context.
Cuối cùng, big-bang generation không có checkpoint (điểm dừng để kiểm tra) tự nhiên. Nếu agent đi sai hướng ngay từ đầu — ví dụ chọn sai chiến lược lưu trữ, hoặc hiểu sai yêu cầu nghiệp vụ — bạn chỉ phát hiện ra sau khi đã nhận toàn bộ output, và phải throw away (bỏ đi) gần như tất cả. Không có điểm nào giữa chừng để bạn nói "dừng lại, hướng này chưa đúng, sửa lại rồi mới đi tiếp".
Mẹo: Nếu bạn thấy mình sắp gõ một prompt dài quá 5-7 câu mô tả toàn bộ feature từ database đến UI, đó là dấu hiệu cần dừng lại và chia nhỏ thành nhiều phase trước khi đưa cho agent.
Mô Hình Staged Implementation
Staged implementation (triển khai theo giai đoạn) là cách tiếp cận ngược lại: thay vì một lượt generate khổng lồ, bạn chia feature thành các phase tuần tự, mỗi phase kết thúc ở một trạng thái code chạy được, có test pass, và được commit độc lập. Với hầu hết feature backend/full-stack, phase tự nhiên đi theo layer kiến trúc:
- Data layer — schema, migration, model, các ràng buộc (constraint) ở tầng dữ liệu.
- Service layer — business logic, quy tắc nghiệp vụ, xử lý side-effect (gửi email, ghi log, gọi hàng đợi/queue...).
- API layer — endpoint REST/GraphQL, validate request/response, mã lỗi, tài liệu API.
- UI/integration layer (nếu có) — component giao diện, gọi API, xử lý trạng thái loading/error.
Mỗi phase không phải là "viết code rồi để đó" — nó phải kết thúc ở trạng thái hoàn chỉnh và tự đứng vững: code compile được, test cho phase đó pass, và quan trọng nhất là được commit vào git với message rõ ràng. Điều này khác về bản chất so với việc viết code nháp rồi commit một lần lúc cuối.
Lợi ích đầu tiên là diff nhỏ, dễ review. Một commit chỉ chứa migration + model cho notification sẽ dễ đọc trong 2 phút, thay vì phải nuốt trọn 800 dòng cùng lúc. Reviewer (kể cả chính bạn, tự review lại) có thể tập trung 100% vào đúng một mối quan tâm (concern) tại một thời điểm.
Lợi ích thứ hai là git history sạch, có ý nghĩa. Khi nhìn lại git log sau này, bạn thấy rõ luồng phát triển: "add notification schema" → "add notification dispatch service" → "add notification REST endpoints" → "wrap notification feature behind feature flag". Đây không chỉ là tài liệu tự nhiên cho người sau, mà còn là dữ liệu quý cho git blame khi cần truy vết một thay đổi cụ thể.
Lợi ích thứ ba, và có lẽ quan trọng nhất trong thực chiến, là khả năng bisect. Nếu ba tuần sau có bug xuất hiện, git bisect có thể nhị phân tìm chính xác commit nào gây ra vấn đề — và vì mỗi commit chỉ chứa một layer, bạn biết ngay lỗi nằm ở tầng nào chỉ bằng cách đọc commit message, không cần đọc lại toàn bộ diff.
Lợi ích cuối cùng là an toàn để dừng ở bất kỳ đâu. Hết giờ làm việc, bị gọi đi họp đột xuất, hoặc phát hiện ra requirement chưa rõ ràng — bạn luôn có thể dừng lại ngay sau một checkpoint, vì tại điểm đó code đã ở trạng thái build được, test pass, và đã nằm an toàn trong git. Không có trạng thái "dở dang, chưa biết sửa gì" bị bỏ lại qua đêm.
Mẹo: Đặt tên phase theo layer kiến trúc, không phải theo "thời gian". Tên phase phải trả lời được câu hỏi "phase này verify cái gì", ví dụ "Phase 2: Service layer — dispatch logic", chứ không phải "Phase 2: tiếp tục hôm qua".
Cách Chỉ Dẫn Agent Commit Từng Phần
Agent mặc định có xu hướng "cày cho xong việc" — nếu bạn không yêu cầu rõ ràng, nó sẽ viết liền một mạch từ đầu đến cuối rồi mới dừng lại báo cáo, kể cả khi bạn đã liệt kê nhiều phase trong yêu cầu ban đầu. Muốn có staged implementation thật sự, bạn phải biến quy trình "code → test → commit → dừng lại chờ review" thành chỉ dẫn tường minh trong prompt, không phải hy vọng agent tự hiểu ý.
Có ba nguyên tắc cần đưa vào prompt hoặc vào file cấu hình agent (như CLAUDE.md, AGENTS.md, hay system prompt tùy công cụ bạn dùng):
1. Yêu cầu chạy test trước khi commit. Đừng để agent commit code chưa từng chạy qua test. Yêu cầu rõ: "run test suite liên quan, chỉ commit khi test pass".
2. Yêu cầu commit message có ý nghĩa, theo convention của team. Nếu team dùng Conventional Commits (feat:, fix:, refactor:...), nói rõ trong prompt để agent tuân theo, tránh commit message chung chung kiểu "update code".
3. Yêu cầu dừng lại và chờ xác nhận sau mỗi phase, thay vì tự động chuyển sang phase tiếp theo. Đây là điểm nhiều người bỏ qua nhất — nếu không nói rõ, agent (đặc biệt khi chạy ở chế độ tự động/autonomous) sẽ coi toàn bộ danh sách phase là một checklist cần hoàn thành liên tục không nghỉ.
Ví dụ một prompt áp dụng cả ba nguyên tắc trên:
We're implementing the notification feature in 4 phases:
1. Data layer (schema + model)
2. Service layer (dispatch logic)
3. API layer (REST endpoints)
4. Feature flag rollout
Right now, only work on Phase 1: Data layer.
Rules for this session:
- After writing the code for this phase, run the full test suite
for the affected modules (`npm run test -- notifications`).
- Only commit if all tests pass. If a test fails, fix it before
committing — do not commit failing code.
- Write a commit message following Conventional Commits format,
e.g. "feat(notifications): add notification schema and model".
- After committing, STOP and summarize what you did in 3-5 bullet
points. Do NOT proceed to Phase 2 automatically — wait for my
explicit go-ahead.
Với các công cụ hỗ trợ file cấu hình đứng (như CLAUDE.md trong Claude Code), bạn có thể đưa nguyên tắc "test trước, commit sau mỗi phase, dừng lại chờ review" vào một lần, áp dụng cho toàn bộ session thay vì phải nhắc lại trong mỗi prompt. Đây chính là lý do các dự án nghiêm túc luôn có một file convention riêng cho agent, tương tự file CLAUDE.md bạn đang đọc ngay trong repo này.
Một sai lầm phổ biến là dùng cụm "làm hết cả 4 phase luôn cho nhanh" khi thấy agent làm phase 1 tốt và muốn tiết kiệm thời gian. Đừng làm vậy với feature có độ phức tạp nghiệp vụ cao hoặc chạm vào dữ liệu nhạy cảm (thanh toán, quyền truy cập, dữ liệu cá nhân). Tốc độ tiết kiệm được ở phase 2-3 không bù lại được chi phí debug nếu agent hiểu sai một giả định nào đó ngay từ phase 1 mà không ai phát hiện kịp thời.
Mẹo: Thêm một dòng "STOP after this phase and wait for my confirmation" ở cuối MỌI prompt staged implementation — kể cả khi bạn nghĩ agent "chắc chắn hiểu ý". Đây là câu phòng thân rẻ nhất bạn có thể thêm vào.
Thực Hành: Staged Implementation Cho Feature Notification
Hãy áp dụng toàn bộ mô hình trên vào một ví dụ cụ thể: xây dựng feature notification cho một task-management app — khi một task được assign cho user, hệ thống gửi thông báo in-app (hiển thị trong ứng dụng) và email. Feature này chạm đủ 3 layer kinh điển: data, service, API — là ví dụ lý tưởng để thực hành staged implementation từ đầu đến cuối, bao gồm cả bước rollout an toàn bằng feature flag.
Bước 1: Định Nghĩa Tất Cả Các Phase Trước Khi Viết Code
Trước khi viết một dòng code nào, hãy để agent giúp bạn vạch ra kế hoạch phase — không phải để agent tự quyết định một mình, mà để bạn có một bản kế hoạch tường minh, review được, trước khi cam kết thực thi. Đây là bước lập kế hoạch (planning), tách biệt hoàn toàn khỏi bước code.
I'm building a "task assignment notification" feature for our task
management app. When a task is assigned to a user, the system should:
- Create an in-app notification record the user sees in a
notification bell/dropdown.
- Send an email notification to the assigned user.
- Let users mark notifications as read.
- Respect a per-user preference to opt out of email notifications
(in-app notifications are always on).
Before writing any code, propose a staged implementation plan broken
into phases by architecture layer (data layer, service layer, API
layer). For each phase, list:
- What will be built.
- What existing files/modules it touches.
- What acceptance criteria / tests define "this phase is done".
Do not write implementation code yet. Just give me the plan as a
numbered list so I can review and approve it first.
Khi agent trả lời, đọc kỹ acceptance criteria (tiêu chí nghiệm thu) của từng phase mà nó đề xuất. Nếu thiếu gì đó (ví dụ: chưa tính đến trường hợp user bị xóa nhưng vẫn còn notification cũ trỏ tới), đây là lúc để sửa kế hoạch — rẻ hơn rất nhiều so với sửa sau khi code đã viết xong. Chỉ khi bạn thực sự đồng ý với plan, mới chuyển sang bước 2.
Bước 2: Thực Thi Phase 1 — Data Layer
Với phase 1 đã được thống nhất, giao đúng phạm vi đó cho agent, không hơn không kém.
Now implement Phase 1: Data layer, from the plan we agreed on.
Scope:
- Create a `notifications` table/migration with fields: id, user_id
(recipient), type (enum: task_assigned, ...), payload (jsonb),
is_read (boolean, default false), created_at.
- Add a `notification_preferences` table/migration: user_id,
email_enabled (boolean, default true).
- Add corresponding models with basic validations (user_id required,
type must be a known enum value).
- Write unit tests for the models: validations, default values,
and the association to the User model.
Do not touch service or API layer yet.
After writing the code:
1. Run the test suite for these two modules.
2. Only commit if tests pass, using a Conventional Commit message.
3. Stop and summarize the schema you created, then wait for my
review before moving to Phase 2.
Trước khi qua bước 3, tự kiểm tra: migration có reversible (rollback được) không? Index cần thiết cho user_id và is_read (vì sẽ query theo hai field này thường xuyên) đã có chưa? Model validation có khớp với ràng buộc ở database không (tránh tình trạng validate ở app nhưng DB vẫn cho insert dữ liệu sai)? Đây là những thứ dễ bị agent bỏ sót nếu không có checklist cụ thể.
Bước 3: Thực Thi Phase 2 — Service Layer
Phase 2 xây dựng business logic thực sự: khi nào tạo notification, khi nào gửi email, cách tôn trọng preference của user.
Now implement Phase 2: Service layer, from the agreed plan.
Scope:
- A `NotificationService` (or equivalent) with a method
`notifyTaskAssigned(task, assignedUser)` that:
- Creates an in-app notification record (always).
- Enqueues an email job ONLY if the user's
`notification_preferences.email_enabled` is true.
- Is idempotent: calling it twice with the same task+user should
not create duplicate in-app notifications (use a uniqueness
check on task_id + user_id + type).
- Wire this service into the existing task-assignment code path
(find where task assignment currently happens and call the
service there) — do not duplicate assignment logic.
- Write unit tests covering: normal case, email opted out, and the
idempotency guard against duplicate calls.
Do not touch API layer yet.
After writing the code, run tests, commit only if green with a
Conventional Commit message, then stop and summarize before I
review and greenlight Phase 3.
Điểm mấu chốt cần verify ở bước này: tính idempotent (gọi nhiều lần không tạo trùng lặp) — đây chính là loại bug "notification gửi email trùng lặp" đã nhắc ở phần đầu bài, và nó luôn nằm ở service layer chứ không phải API layer. Kiểm tra kỹ test case idempotency trước khi merge, đừng chỉ tin vào lời agent nói "đã handle".
Bước 4: Thực Thi Phase 3 — API Layer
Phase 3 phơi bày (expose) notification ra ngoài qua endpoint, để frontend/mobile có thể hiển thị và cho phép user tương tác (đánh dấu đã đọc).
Now implement Phase 3: API layer, from the agreed plan.
Scope:
- GET /api/notifications — list current user's notifications,
paginated, newest first, filterable by ?unread=true.
- PATCH /api/notifications/:id/read — mark a single notification
as read. Must return 403/404 if the notification doesn't belong
to the requesting user (do not leak other users' notifications).
- PATCH /api/notifications/read-all — mark all of current user's
notifications as read.
- Validate request params/query and return clear 4xx errors with
a consistent error response shape matching our existing API
conventions.
- Write integration tests for all 3 endpoints, including the
ownership check (user A cannot mark user B's notification as read).
Do not touch frontend/UI yet.
After writing the code, run the full integration test suite, commit
only if green using a Conventional Commit message, then stop and
summarize before I review.
Đây là layer dễ lộ lỗ hổng bảo mật nhất — luôn tự kiểm tra riêng phần "ownership check" (không cho user A thao tác lên notification của user B) bằng cách đọc test case đó, đừng chỉ tin summary của agent. Đồng thời kiểm tra format lỗi trả về có nhất quán với các endpoint khác trong hệ thống hay không, vì đây là chi tiết agent dễ tự sáng tạo ra một convention riêng nếu không được nhắc.
Bước 5: Verify Toàn Bộ Feature Theo Acceptance Criteria
Sau khi cả 3 phase đã commit riêng biệt, đừng vội coi feature đã xong. Đưa toàn bộ acceptance criteria đã thống nhất ở Bước 1 trở lại cho agent, yêu cầu tự đối chiếu (self-check) một cách có hệ thống — đây là bước verification tổng thể trước khi tính đến rollout.
Here's the full acceptance criteria list we agreed on for the
notification feature:
1. Assigning a task creates an in-app notification for the assignee.
2. If email_enabled is true for that user, an email is also sent.
3. If email_enabled is false, no email is sent, but the in-app
notification is still created.
4. Calling the assignment flow twice does not create duplicate
notifications.
5. GET /api/notifications returns only the current user's own
notifications, paginated.
6. A user cannot mark another user's notification as read via the API.
7. Marking a notification (or all) as read persists correctly and
is reflected on next GET.
Go through the current implementation (across all 3 phases already
committed) and, for each criterion above, tell me:
- Which test(s) currently cover it.
- Whether the criterion is fully satisfied, partially, or not covered.
- If not fully covered, propose the missing test case (don't write
code yet, just list what's missing).
Report as a checklist, one line per criterion.
Đọc kỹ output này. Nếu agent báo "partially covered" cho bất kỳ mục nào, đừng bỏ qua — yêu cầu viết thêm test case cho đúng gap đó trước khi coi feature hoàn chỉnh. Bước verify này chính là lý do vì sao staged implementation vượt trội hơn hẳn big-bang generation: bạn có một bộ acceptance criteria cụ thể để đối chiếu, thay vì chỉ "cảm thấy" feature chạy được qua vài lần test tay.
Bước 6: Dùng Feature Flag Để Rollout An Toàn Theo Giai Đoạn
Feature đã đúng về mặt chức năng không có nghĩa là nên bật cho 100% user ngay lập tức. Bước cuối cùng là bọc feature trong một feature flag (cờ tính năng — cơ chế bật/tắt tính năng mà không cần deploy lại code) để rollout (triển khai) dần dần, có thể tắt ngay nếu phát hiện vấn đề ở production.
Wrap the notification feature (in-app + email) behind a feature
flag called "task_assignment_notifications", using our existing
feature flag system [name your provider/library here, e.g.
LaunchDarkly / Unleash / a simple config-based flag].
Requirements:
- If the flag is off for a user, task assignment should behave
exactly as before this feature existed (no notification created,
no email sent, no errors).
- If the flag is on, the full feature from Phases 1-3 applies.
- Add a test that explicitly verifies both the flag-off and
flag-on code paths.
- Default the flag to off in all environments; I will enable it
manually starting with an internal team first.
After implementing, run tests, commit with a Conventional Commit
message once green, then summarize how to turn the flag on for a
specific user segment.
Với flag đã sẵn sàng, rollout thực tế nên đi theo từng nấc: bật cho nội bộ team/QA trước để bắt lỗi trong môi trường an toàn, sau đó bật cho 5-10% user thật để quan sát metric (tỷ lệ lỗi gửi email, độ trễ tạo notification), rồi mới tăng dần lên 50% và 100%. Ở mỗi nấc, có một khoảng thời gian quan sát (ví dụ 24-48 giờ) trước khi tăng tiếp — đây chính là tinh thần "checkpoint" áp dụng ở cấp độ rollout, không chỉ ở cấp độ code.
Mẹo: Sau khi hoàn thành cả 6 bước, xem lại
git log --onelinecho toàn bộ feature — nếu một người chưa từng tham gia dự án đọc được lịch sử commit và hiểu ngay feature được xây dựng theo trình tự nào, đó là dấu hiệu staged implementation đã được thực hiện đúng cách.
Những điểm chính
- Big-bang AI generation tạo diff khổng lồ khó review, khó debug, không có checkpoint, và dễ sinh lỗi do agent bị quá tải context window khi phải giữ toàn bộ feature trong đầu cùng lúc.
- Staged implementation chia feature thành phase theo layer kiến trúc (data → service → API → UI), mỗi phase kết thúc ở trạng thái chạy được, test pass, và commit độc lập.
- Diff nhỏ dễ review hơn, git history sạch hơn, và
git bisecttrở nên hữu dụng thực sự khi mỗi commit chỉ đại diện cho một mối quan tâm (concern) duy nhất. - Luôn ra chỉ dẫn tường minh cho agent: chạy test trước khi commit, dùng commit message có convention rõ ràng, và dừng lại chờ xác nhận sau mỗi phase thay vì tự động chạy hết mọi phase liên tục.
- Bắt đầu bằng bước lập kế hoạch (planning) tách biệt khỏi bước code — để agent đề xuất phase và acceptance criteria trước, bạn review và chỉnh sửa trước khi cam kết thực thi.
- Sau khi hoàn thành tất cả phase, luôn có một bước verify tổng thể, đối chiếu implementation với acceptance criteria ban đầu — đừng chỉ tin cảm giác "chắc là xong rồi".
- Feature flag không phải bước phụ — nó là lớp an toàn cuối cùng, cho phép rollout dần theo phân khúc user và tắt ngay lập tức nếu phát hiện sự cố ở production.