Vibe coding không phải là gõ đại — cái khó là gọi đúng tên vấn đề cho AI
Cùng một tính năng, đổi cách mình gọi tên vấn đề thôi, AI có thể viết ra hai codebase khác hẳn nhau. Đây là lý do, và cẩm nang tra keyword theo triệu chứng.
Vibe coding không phải là gõ đại — cái khó là gọi đúng tên vấn đề cho AI
Cùng một tính năng, đổi cách mình gọi tên vấn đề thôi, AI có thể viết ra hai codebase khác hẳn nhau.
Vibe coding có một đoạn rất đã. Mình kể app cần gì bằng tiếng người, Cursor hay Claude Code tự dựng màn hình, API, database, validation. Một người không sống trong code mỗi ngày bỗng có thể biến ý tưởng thành thứ chạy được, cảm giác đó thiệt sự mở ra nhiều cửa.
Nhưng app gặp người dùng thật thì câu chuyện đổi nhịp. Có người bấm thanh toán hai lần vì mạng lag, thế là bị trừ tiền hai lần và tạo hai đơn trùng. Mình sửa một dòng logic giảm giá trong giỏ hàng, màn admin đơn hàng lại trắng vì một chỗ khác đang dựa vào dữ liệu cũ. Rồi nhờ AI fix một bug nhỏ, nó vá bằng thêm if-else, vài vòng như vậy codebase thành đĩa mì spaghetti mà chính mình nhìn cũng không dám đụng.
Lúc đó dễ kết luận là AI code dở. Nhưng mình nhận ra AI thường làm đúng thứ mình yêu cầu, chỉ là yêu cầu của mình đang nói ở độ phân giải quá thấp.
Vấn đề không nằm ở số chữ, mà ở tên mình đặt cho nó
LLM đã đọc một lượng khổng lồ code production, tài liệu kiến trúc và những bài viết của người làm phần mềm rất giỏi. Trong cái không gian đó, lời giải tốt nhất thường đi kèm với những cái tên chuẩn: idempotency, transaction, debounce, soft delete, Finite State Machine.
Nếu mình nói “đừng để khách bấm hai lần”, AI có thể tự nghĩ ra disable button ở frontend. Cách đó nhìn có vẻ ổn, nhưng refresh trang hay gọi API trực tiếp là thủng ngay. Còn nếu mình gọi đúng đây là bài toán idempotency cho payment request, AI biết phải nghĩ tới idempotency key, unique constraint, xử lý retry và ranh giới transaction.
Một keyword kỹ thuật giống như file nén vậy. Nó không làm AI thông minh thêm trong khoảnh khắc đó, nhưng nó mở đúng ngăn kiến thức mà mình cần. Thay vì bắt AI đoán ý qua năm trăm chữ mô tả, mình đưa cho nó một nhãn đã có cả lịch sử giải quyết vấn đề phía sau.
Một tính năng đổi trạng thái đơn hàng, hai kết quả rất khác
Lấy ví dụ quen thuộc: đơn hàng đi từ Chờ duyệt sang Đang giao, rồi Đã giao. Mình cũng cần chặn khách hoặc nhân viên chuyển bậy, như từ Đã giao quay về Chờ duyệt.
Prompt kiểu cũ của mình thường là thế này:
Viết cho mình tính năng đổi trạng thái đơn hàng từ Chờ duyệt
sang Đang giao sang Đã giao, chặn khách chuyển bậy.
AI vẫn có thể làm ra thứ chạy được. Nó tạo endpoint, kiểm tra status hiện tại, rồi viết một chuỗi điều kiện kiểu “nếu đang chờ duyệt thì được sang đang giao”, “nếu đang giao thì được sang đã giao”. Khi số trạng thái còn ít, mình nhìn qua thấy hợp lý.
Nhưng ba ngày sau business thêm Hoàn hàng, hoặc cho phép hủy đơn ở một số điểm, các nhánh if-else bắt đầu lồng nhau. Có transition bị quên, có chỗ admin được đi qua còn customer lại lọt vào, có chỗ trả về lỗi chung chung. Code không hỏng vì AI thiếu khả năng viết điều kiện; nó hỏng vì mình đã đưa nó một bài toán kiến trúc nhưng gọi bằng ngôn ngữ của một màn hình UI.
Lúc mình đổi prompt thành thế này, kết quả khác hẳn:
Dùng mô hình Finite State Machine (FSM) cho order status.
Khai báo transition table hợp lệ bằng enum, chặn mọi chuyển
trạng thái trái phép và trả về mã lỗi rõ ràng cho từng transition.
Ngay từ đầu, AI có một khung để bám vào. Nó tách danh sách trạng thái ra thành enum, gom luật chuyển trạng thái vào transition table, và mọi request đều đi qua một chỗ kiểm tra chung. Khi thêm Hoàn hàng, mình thêm state và luật transition tương ứng thay vì đi săn if-else khắp codebase.
Cái hay không phải FSM thần thánh hơn if-else. Với một toggle bật/tắt đơn giản, lôi FSM vào có khi là over-engineer. Nhưng order status là một quy trình có trạng thái, có bước hợp lệ và bước bị cấm. Gọi đúng tên bài toán giúp AI chọn đúng cấu trúc trước khi nó bắt đầu đẻ code.
Mình đổi cách nói chuyện với AI theo hai bước
Bây giờ gặp một feature có vẻ đụng tới dữ liệu, tiền hoặc quy trình, mình không nhảy thẳng vào “viết code đi”. Mình bắt AI làm vai senior architect trước.
Bước một là chẩn đoán. Mình hỏi: feature này có rủi ro production gì, đâu là edge case, pattern nào phù hợp, và vì sao. Đoạn này giúp mình thấy rõ khi nào cần idempotency, khi nào cần transaction, lúc nào chỉ cần debounce, hay lúc workflow đã đủ phức tạp để dùng state machine.
Bước hai mới là thi công. Sau khi chốt pattern, mình yêu cầu triển khai đúng theo thiết kế đó: schema nào, constraint nào, error handling ra sao, test các transition nào. AI vẫn viết code, nhưng giờ nó không còn tự chọn kiến trúc trong im lặng nữa.
Mình cũng phải biết điểm dừng. Không phải mọi form đều cần event-driven architecture, không phải mọi record xóa đi đều cần soft delete. Kỹ năng ở đây là gọi đúng vấn đề với độ phức tạp thật của nó, chớ không phải nhét càng nhiều thuật ngữ càng tốt.
Vibe coding sâu hơn là học cách đặt tên
Trọng tâm đang dịch dần khỏi chuyện gõ từng dòng cú pháp. Thứ tạo ra khác biệt là quyết định kiến trúc nào đáng dùng, và gọi tên vấn đề đủ chính xác để AI truy xuất phần kiến thức tốt nhất của nó.
Mình không nghĩ người build bằng AI cần ngồi học thuộc code như lập trình viên. Nhưng mình cần có một cuốn từ điển nối giữa triệu chứng đời thường và keyword kỹ thuật: gõ search mà server quay cuồng thì nghĩ tới Debounce; sợ xóa nhầm data thì tra Soft Delete; quy trình cứ bị nhảy cóc thì xem FSM.
Cẩm nang thuật ngữ đầy đủ mình để ngay bên dưới, và có bản tải về để lúc đang đau ở đâu thì tra từ triệu chứng đó thay vì mò prompt bằng cảm giác. Còn bạn, cái “bức tường” nào trong vibe coding làm bạn thấy app bắt đầu mong manh nhất?
Cẩm nang thuật ngữ kỹ thuật (tra theo triệu chứng)
Bảng dưới đây tra theo cái mình đang đau, không phải theo môn học. Gặp triệu chứng nào thì gọi tên pattern đó cho AI. Bản gốc tải về đầy đủ ở đây.
Quy trình & luồng nghiệp vụ
| Triệu chứng | Keyword | Bản chất & lợi ích |
|---|---|---|
Quy trình nhiều bước (đơn hàng, duyệt bài, vé hỗ trợ), code bắt đầu đầy if-else lồng nhau, dễ nhảy cóc bước hoặc trạng thái mâu thuẫn. |
Finite State Machine (FSM) | AI tạo enum trạng thái + transition table hợp lệ, chặn mọi trạng thái bất hợp lệ. |
| Người dùng bấm nút gửi (thanh toán, tạo bài, gửi mail) nhiều lần do mạng lag, làm trùng bản ghi hoặc trừ tiền 2 lần. | Idempotency / Idempotent Request | Thêm Idempotency-Key hoặc khóa giao dịch theo token duy nhất, gọi 10 lần vẫn chỉ xử lý 1 lần. |
| Một thao tác đụng nhiều bảng (trừ ví → tạo hóa đơn → giảm kho), lỗi ở bước giữa làm dữ liệu cọc cạch. | Atomic Transaction (ACID) / Rollback | Gom mọi thao tác vào 1 transaction. Thành công thì commit, lỗi thì rollback về trạng thái cũ. |
| Tác vụ nặng (gửi mail, xuất PDF, import Excel 10.000 dòng) làm treo màn hình và nghẽn request HTTP. | Background Job / Async / Message Queue | Trả thành công ngay cho user, đẩy việc nặng vào queue cho worker xử lý ngầm. |
| Dữ liệu cấu hình hoặc bản ghi nhạy cảm dễ bị sửa ngoài ý muốn ở nhiều chỗ. | Immutable Data / Read-only Pattern | Đóng băng object, không cho mutate trực tiếp, tránh side-effect. |
Hiệu năng & giao diện
| Triệu chứng | Keyword | Bản chất & lợi ích |
|---|---|---|
| Gõ ô tìm kiếm, mỗi ký tự bắn 1 request làm server quá tải, kết quả nhảy liên tục. | Debounce | Đợi user ngừng gõ một khoảng (vd 300ms) mới gửi request một lần. |
| Bấm Like / thả tim / lưu bài, phải chờ 1-2 giây quay vòng mới thấy đổi. | Optimistic UI Update | Đổi giao diện ngay khi click, gửi request ngầm; server báo lỗi thì mới rollback UI kèm thông báo. |
| Danh sách hàng chục ngàn phần tử (chat, lịch sử giao dịch) làm đơ trình duyệt. | Virtual Scrolling / Windowing | Chỉ render vào DOM những phần tử đang nằm trong tầm nhìn. |
| Trang load xong bị nhảy giật do ảnh/nội dung xuất hiện muộn, user bấm nhầm. | Cumulative Layout Shift (CLS) / Skeleton Screen | Dựng sẵn khung xám đúng kích thước nội dung trong lúc chờ tải. |
| Cuộn hoặc di chuột liên tục sinh hàng trăm sự kiện mỗi giây làm UI lag. | Throttle | Giới hạn tần suất chạy hàm theo chu kỳ cố định (vd tối đa 1 lần mỗi 100ms). |
Bền bỉ & chịu lỗi
| Triệu chứng | Keyword | Bản chất & lợi ích |
|---|---|---|
| Sửa module này vô tình làm hỏng module khác; khó thêm loại đối tượng mới. | Open-Closed Principle (OCP) / Loose Coupling | Mở rộng bằng module mới kế thừa interface, không sửa trực tiếp code đã chạy ổn. |
| Tích hợp bên thứ 3 (cổng thanh toán, AI API, SMS); đối tác sự cố là cả hệ thống chết theo. | Circuit Breaker & Graceful Degradation | Ngắt kết nối khi lỗi vượt ngưỡng, chuyển sang fallback an toàn thay vì nghẽn cả server. |
| Mạng chập chờn khiến gọi API thỉnh thoảng thất bại ngẫu nhiên. | Retry with Exponential Backoff & Jitter | Gọi lại với thời gian chờ tăng dần + độ trễ ngẫu nhiên để tránh dồn tải lên server đích. |
| Cần nhiều nhà cung cấp cùng loại (Stripe, MoMo, ZaloPay) mà không muốn code rải rác. | Strategy / Adapter Pattern | Định nghĩa một interface chung (vd IPaymentService) và tạo adapter riêng cho từng nhà cung cấp. |
| Bot spam bấm request liên tục làm cạn CPU/RAM. | Rate Limiting / Token Bucket | Đặt ngưỡng số request tối đa theo IP hoặc user token trong một đơn vị thời gian. |
Dữ liệu & bảo mật
| Triệu chứng | Keyword | Bản chất & lợi ích |
|---|---|---|
| User xóa nhầm hoặc có tranh chấp, cần giữ lịch sử nhưng không hiện trên UI thường. | Soft Delete | Thêm deleted_at hoặc cờ is_deleted thay vì DELETE vật lý. |
| Bảng hàng trăm ngàn dòng, query lọc theo ngày/trạng thái chạy quá chậm. | Database Indexing & Query Plan Optimization | Tạo index đúng cột hay dùng trong WHERE / JOIN. |
Lấy danh sách lớn phân trang, OFFSET lớn làm query rất nặng. |
Cursor-based / Keyset Pagination | Phân trang theo ID hoặc mốc thời gian của phần tử cuối, không đếm nhảy trang. |
| Dữ liệu ít đổi (danh mục, bảng giá) nhưng mỗi lần load lại gọi database. | In-memory Cache with TTL | Cache tạm (Redis/memory) có hạn tự hủy để giảm tải database. |
| Input từ form/URL có nguy cơ chứa mã độc hoặc sai định dạng gây crash. | Input Validation & Schema Sanitization | Dùng schema validator (Zod, Joi, Pydantic) kiểm tra chặt kiểu dữ liệu trước khi xử lý. |
Kiểm thử & quan sát
| Triệu chứng | Keyword | Bản chất & lợi ích |
|---|---|---|
| Muốn các hàm tính giá / chiết khấu / thuế luôn ra kết quả chính xác. | Unit Testing & Boundary Value Analysis | Viết test tự động tập trung vào ca biên (số âm, số 0, số cực lớn, ký tự lạ). |
| Lỗi trên production nhưng không tái hiện được vì không biết user đã làm gì. | Structured Logging & Audit Trail | Log dạng JSON có cấu trúc (timestamp, request ID, user ID, context) thay vì log thô. |
| Sợ sửa một đoạn nhỏ làm phát sinh lỗi ngầm ở tính năng cũ. | Regression Testing & CI/CD Pipeline | Chạy lại toàn bộ test tự động mỗi lần có code mới tích hợp. |
Bộ rule dán vào AI (Cursor / Claude Code / Windsurf)
Copy đoạn dưới vào Custom Instructions, file .cursorrules / .windsurfrules, hoặc tạo một agent trong Claude Code:
Role: Senior Software Architect & Technical Mentor.
Context: I am a Vibe Coder / Product Builder with strong domain
knowledge but a non-traditional technical background.
Instructions:
1. When I describe a feature in plain business language, DO NOT
just write ad-hoc code.
2. First, identify 2-3 standard architecture patterns / technical
keywords that solve it properly (e.g. Idempotency, FSM, Debounce,
Soft Delete, Circuit Breaker).
3. Briefly explain WHY each pattern matters for production
resilience, in 1-2 bullets.
4. Implement strictly adhering to those stated patterns.