Một app vibe-coded 6 tháng vẫn có thể có user, có doanh thu, nhưng repo lại thành một mớ rất khó bàn giao. Đây không phải chuyện hiếm: AI giúp mình thêm tính năng nhanh, nhưng nếu không có ranh giới kiến trúc, nó cũng rất giỏi tạo thêm file mới, hàm trùng lặp và nhiều cách xử lý cùng một vấn đề.
Tin tốt là không phải lúc nào cũng cần rewrite từ đầu. Nếu sản phẩm đang chạy và khách hàng vẫn dùng được, hướng hợp lý thường là “ổn định hóa rồi bóc tách dần”, thay vì đập đi xây lại trong hoảng loạn.
Vấn đề thật không phải là AI viết code dở
Vấn đề là trong 6 tháng, repo thiếu một người hoặc một quy trình đóng vai trò “kiến trúc sư”. Khi mỗi prompt chỉ tập trung vào tính năng trước mắt, AI thường tối ưu cho việc làm cho feature chạy được ngay:
- thêm một helper mới thay vì tìm helper cũ;
- copy logic vì ngữ cảnh hiện tại không thấy phần đã có;
- vá nhanh ở UI thay vì sửa đúng ở domain/service;
- tạo pattern thứ 3, thứ 4 vì không có chuẩn nào được nhắc lại;
- bỏ qua test vì mục tiêu là demo nhanh.
Kết quả là app vẫn chạy, nhưng chi phí thay đổi tăng rất nhanh. Đến lúc onboard dev mới, người đó không chỉ đọc code, mà phải đoán lịch sử của hàng trăm quyết định nhỏ không được ghi lại.
Đừng vội rewrite nếu app đang có doanh thu
Rewrite nghe hấp dẫn vì nó tạo cảm giác “làm sạch một lần cho xong”. Nhưng với sản phẩm đang có user, rewrite thường có 3 rủi ro:
- mất vài tuần hoặc vài tháng mà không tạo giá trị mới;
- tái tạo bug cũ vì behavior thực tế không được ghi lại;
- cuối cùng lại sinh ra một codebase mới cũng thiếu kỷ luật nếu cách làm không đổi.
Rewrite chỉ nên là lựa chọn khi repo hiện tại không thể build, không thể deploy, không thể sửa lỗi nghiêm trọng, hoặc mô hình dữ liệu/sản phẩm đã sai tận gốc. Còn nếu app vẫn chạy và doanh thu đang vào, mình sẽ ưu tiên kế hoạch cứu repo theo từng lớp.
Kế hoạch cứu repo trong 7 bước
1. Đóng băng feature trong một khoảng ngắn
Không cần dừng sản phẩm quá lâu, nhưng nên có một “tuần ổn định” hoặc vài ngày chỉ làm cleanup. Trong thời gian đó, mọi thay đổi mới phải phục vụ một mục tiêu: giảm rủi ro khi sửa code.
Nếu vẫn tiếp tục prompt thêm feature trong lúc repo đang rối, mình chỉ đang đổ thêm bê tông lên nền móng chưa kiểm tra.
2. Vẽ bản đồ hệ thống hiện tại
Trước khi refactor, hãy mô tả app như nó đang tồn tại, không phải như mình muốn nó tồn tại.
Một file ARCHITECTURE.md tối thiểu nên có:
# Architecture
## Main flows
- Signup/login
- Billing
- Core user workflow
- Admin workflow
## Important folders
- /app: routes and screens
- /components: reusable UI
- /lib: external integrations
- /services: business logic
- /db: schema and migrations
## Risky areas
- Billing state
- Auth/session handling
- Data deletion/export
- Background jobs
Chưa cần hoàn hảo. Mục tiêu là để dev mới và AI đều có cùng một bản đồ khi làm việc.
3. Tìm các vùng nguy hiểm trước
Đừng refactor toàn bộ repo theo cảm hứng. Hãy ưu tiên những nơi nếu sai sẽ gây thiệt hại thật:
- auth và phân quyền;
- billing, subscription, invoice;
- dữ liệu người dùng;
- migration database;
- webhook từ Stripe, email, payment, storage;
- logic tính tiền, giới hạn quota, quyền truy cập.
Những phần này nên có test trước khi sửa mạnh. Với vibe-coded app, mình đặc biệt không muốn AI tự do “dọn” billing nếu chưa có test khóa behavior.
4. Viết characterization tests
Nếu chưa có test, đừng bắt đầu bằng test lý tưởng. Hãy viết test ghi nhận hành vi hiện tại trước.
Ví dụ:
Given user has active subscription
When Stripe sends invoice.payment_failed
Then account status becomes past_due
And access is limited after grace period
Loại test này giúp mình biết khi refactor có làm lệch behavior hay không. Sau khi đã có lưới an toàn, cleanup mới bớt đáng sợ.
5. Chuẩn hóa một pattern cho mỗi việc
Repo rối thường có nhiều cách làm cùng một chuyện. Hãy chọn một pattern chính thức rồi ghi lại trong CONVENTIONS.md:
# Conventions
## Data access
Use /services/* for business operations. UI components must not call the database directly.
## API errors
Return typed errors from service layer. Do not throw raw provider errors into UI.
## Components
Shared components live in /components/shared. Route-only components stay near the route.
## AI changes
Before adding a new helper, search for existing helpers and update this file if a new pattern is introduced.
Từ đó, mỗi lần sửa một file, mình đưa nó về pattern chuẩn. Không cần biến cả repo thành sạch ngay lập tức.
6. Dọn theo lát cắt, không dọn theo thư mục
Một lỗi phổ biến là mở /lib hoặc /components ra rồi refactor hàng loạt. Cách đó dễ vỡ vì mình đang sửa nhiều behavior cùng lúc.
Tốt hơn là chọn một flow cụ thể, ví dụ “subscription checkout”, rồi làm trọn lát cắt:
- route/UI liên quan;
- service xử lý logic;
- webhook hoặc background job;
- database schema;
- test cho flow đó;
- tài liệu ngắn về flow.
Sau mỗi lát cắt, app vẫn deploy được. Đây là cách biến một repo hỗn loạn thành repo có vùng sạch ngày càng lớn.
7. Đổi cách dùng AI từ “thêm code” sang “giữ kỷ luật”
AI vẫn rất hữu ích, nhưng prompt nên thay đổi. Thay vì chỉ nói “build this feature”, hãy bắt AI làm theo checklist:
Before coding:
1. Read ARCHITECTURE.md and CONVENTIONS.md.
2. Search for existing patterns that solve similar problems.
3. Propose the smallest change plan.
4. Do not create a new abstraction unless you explain why existing ones do not fit.
5. Add or update tests for changed behavior.
Sau khi AI sửa xong, tiếp tục yêu cầu:
Review your diff for duplicated logic, inconsistent naming, missing tests, and files that should not have been touched.
Điểm mấu chốt: AI không chỉ là máy sinh code. Mình phải dùng nó như một reviewer phụ để ép repo quay về chuẩn.
Checklist thực tế trước khi onboard dev mới
Nếu sắp thuê hoặc mời dev vào cứu repo, mình sẽ chuẩn bị các thứ này trước:
- app build được từ README mới nhất;
- có
.env.examplesạch, không lộ secret; - có sơ đồ thư mục và các flow chính;
- liệt kê 5 khu vực rủi ro nhất;
- có test tối thiểu cho auth, billing, data write quan trọng;
- có danh sách “không đụng vào nếu chưa hỏi”;
- có backlog cleanup theo từng lát cắt, không phải một ticket mơ hồ kiểu “refactor codebase”.
Dev mới mở repo mà thấy những thứ này sẽ dễ giúp mình hơn rất nhiều. Họ có thể vẫn chê code rối, nhưng ít nhất họ biết phải bắt đầu từ đâu.
Kết luận
Vibe coding không tự động tạo ra technical debt, nhưng nó làm technical debt tích lũy nhanh hơn nếu mình chỉ thưởng cho tốc độ ship. Khi app đã có user và doanh thu, mục tiêu không phải là xấu hổ vì repo bừa, mà là chuyển từ chế độ “prototype liên tục” sang chế độ “sản phẩm có thể bảo trì”.
Nếu phải chọn một việc làm ngay hôm nay, mình sẽ không rewrite. Mình sẽ viết ARCHITECTURE.md, chọn một flow rủi ro cao nhất, thêm characterization tests cho flow đó, rồi dọn từng lát cắt nhỏ. Sau vài vòng như vậy, repo bắt đầu có đường để thoát ra thay vì chỉ có cảm giác muốn đập đi làm lại.
Top comments (0)