Một startup founder khoe với tôi tháng trước: "Team mình 3 người, dùng AI xong deliver được như team 15 người." Tôi hỏi lại: "Codebase hiện tại bao nhiêu tuổi?" Anh ta ngập ngừng: "8 tháng." Tôi hỏi tiếp: "Có ai dám thêm feature lớn vào core flow không?" Câu trả lời là im lặng.

Đây không phải câu chuyện cá biệt. Vibe coding - cái trào lưu prompt-to-production đang thịnh hành - tạo ra sản phẩm nhanh, nhưng không có gì miễn phí. Debt tích lũy nhanh hơn 10x so với viết tay, và không ai nghĩ đến điều đó khi đang hưng phấn với tốc độ deliver.

Bài này không phải để chê AI. Tôi dùng Claude Code mỗi ngày. Nhưng với tư cách CTO, tôi cần nhìn thẳng vào vấn đề mà nhiều team đang tránh né.

Taxonomy Technical Debt Mới Trong Kỷ Nguyên AI

Technical debt truyền thống có 4 loại mà Martin Fowler đã phân loại: reckless/prudent và deliberate/inadvertent. Trong thời AI, cần thêm 3 loại mới.

AI-Generated Debt

Đây là debt phổ biến nhất và cũng khó nhận ra nhất. AI generate code hoạt động đúng nhưng không nhất quán với pattern của codebase. Cùng một business logic, AI có thể viết theo 3 cách khác nhau tùy prompt, tùy context window nó nhìn thấy.

Ví dụ thực tế: Một fintech team tôi tư vấn có 47 cách xử lý error khác nhau trong một codebase. Khi tôi hỏi tại sao, câu trả lời là: "Mỗi lần AI generate ra một kiểu, mình thấy chạy được thì merge." Không ai ngồi review pattern-level.

Dấu hiệu nhận biết: chạy lệnh sau và đếm số lượng inconsistency:

# Tìm các cách handle error khác nhau
grep -r "catch\|\.catch\|try {" src/ | grep -v node_modules | wc -l
 
# Tìm các pattern async khác nhau
grep -rE "async/await|\.then\(|Promise\.all" src/ --include="*.ts" | \
  awk -F: '{print $1}' | sort | uniq -c | sort -rn | head -20

Nếu một file có cả ba pattern, đó là AI-generated debt đang ẩn trong code.

Prompt Debt

Ít người nói đến loại này: khi bạn không document tại sao code được viết theo một cách nhất định vì lý do nằm trong prompt, không nằm trong code.

Tháng trước, một senior dev trong team của client tôi mất 2 ngày debug một edge case kỳ lạ. Sau cùng phát hiện ra: AI đã generate code với một hardcode workaround vì prompt gốc có câu "tạm thời bỏ qua validation X". Câu đó không xuất hiện trong code, không có comment, không có ticket. Nó chỉ tồn tại trong lịch sử chat của người đã xin nghỉ việc 3 tháng trước.

Prompt debt nghĩa là: context để hiểu tại sao code tồn tại theo dạng hiện tại không được lưu lại ở bất kỳ đâu có thể access được.

Eval Debt

Loại thứ ba, và nguy hiểm nhất về dài hạn: không có test để verify rằng AI-generated code làm đúng điều nó được yêu cầu làm - chỉ verify rằng nó không crash.

Coverage cao không đồng nghĩa eval tốt. Test "không throw exception" khác với test "tính toán đúng business logic". AI rất giỏi generate test cho happy path, rất kém generate test cho business invariants.

Đo Velocity Thực Sự vs Perceived Velocity

Đây là chỗ nhiều CTO bị đánh lừa bởi số liệu của chính mình.

Perceived velocity: số features shipped per sprint tăng 3x sau khi adopt AI tools.

Real velocity: tính thêm thời gian debug unexplained behaviors, thời gian onboard người mới vào codebase, thời gian hotfix production incidents mà root cause là AI-generated edge case không được test.

Công thức tôi dùng để đo thực chất hơn:

Real Velocity = Features Shipped / (Dev Time + Bug Fix Time + Onboarding Tax)

Trong đó Onboarding Tax là thời gian trung bình để một dev mới đóng góp được feature đầu tiên mà không cần pair với người cũ. Với AI-heavy codebase không có documentation prompt context, con số này tăng đáng kể.

Cách đo cụ thể: lấy 3 sprint gần nhất, tính:

  1. Số story points delivered
  2. Số giờ spent on bug fixes (đặc biệt bugs có label "unexpected behavior")
  3. Số giờ của senior dev spent explaining codebase cho junior/new hire

Nếu (2) + (3) chiếm hơn 40% total dev hours, perceived velocity đang che giấu velocity thực.

Chiến Lược Refactor AI-Generated Code

Refactor AI code khác refactor code người viết ở một điểm quan trọng: bạn thường không biết tại sao code được viết như vậy.

Nguyên Tắc "Đọc Trước Khi Tin"

Trước khi refactor bất kỳ đoạn AI code nào, phải đọc và hiểu toàn bộ, không chỉ phần cần thay đổi. AI hay inject hidden dependencies - một function trông như stateless nhưng thực ra đang read/write global state theo cách không obvious.

Quy Trình 3 Bước

Bước 1: Characterization tests. Trước khi đụng vào code, viết tests mô tả behavior hiện tại - dù behavior đó có đúng hay không. Mục đích không phải test correctness, mà là lock down behavior để refactor không vô tình thay đổi output.

// Characterization test - không cần đẹp, chỉ cần capture behavior
describe("CHARACTERIZATION: calculateDiscount (do not modify these tests)", () => {
  it("returns 0 for amount < 100", () => {
    expect(calculateDiscount(99)).toBe(0);
  });
  it("returns 10 for amount >= 100 (observed, not verified correct)", () => {
    expect(calculateDiscount(100)).toBe(10);
  });
});

Bước 2: Identify seams. Tìm chỗ có thể tách module ra mà không cần hiểu toàn bộ logic.

Bước 3: Migrate incrementally, với feature flags. Không bao giờ big-bang refactor AI code. Quá nhiều implicit behavior bị ẩn.

Anti-Pattern Cần Tránh

Đừng prompt AI để refactor AI code mà không có human review kỹ. Tôi đã thấy loop như thế này: AI A generate code -> bug xuất hiện -> dùng AI B refactor -> bug mới xuất hiện -> dùng AI C fix -> codebase trở thành thứ không ai hiểu. Không có điểm dừng có chủ ý.

Code Ownership Trong Team Dùng AI Nhiều

Câu hỏi thực tế: khi một PR được tạo bởi 80% AI và 20% human editing, ai là owner?

Câu trả lời tôi enforce trong team: người merge là owner, không phải người prompt.

Điều này có nghĩa: nếu bạn merge một PR mà bạn không hiểu tại sao mỗi phần của nó tồn tại, bạn đang tích lũy ownership debt. Khi có incident, bạn phải debug code của chính mình - mà thực ra bạn chưa bao giờ thực sự đọc.

Tôi áp dụng rule sau trong code review checklist:

PR Acceptance Checklist (AI-heavy teams):
[ ] Reviewer có thể giải thích từng function trong PR làm gì không?
[ ] Có comment giải thích business reason cho non-obvious code không?
[ ] Prompt/context gốc được link trong PR description không?
[ ] Edge cases được test, không chỉ happy path?
[ ] Pattern có nhất quán với phần còn lại của codebase không?

Point cuối quan trọng hơn mọi người nghĩ. AI không biết convention của codebase bạn trừ khi bạn feed context đầy đủ. Và context window có giới hạn.

Khi Nào Cần "AI Detox" - Viết Lại Từ Đầu

Đây là câu hỏi tôi hay bị hỏi nhất, và câu trả lời của tôi luôn là: hiếm hơn bạn nghĩ, nhưng có những dấu hiệu không thể bỏ qua.

Dấu Hiệu Cần Xem Xét Rewrite

Dấu hiệu 1: Không ai trong team có thể explain full request lifecycle của core feature mà không cần đọc code. Đây nghĩa là architectural intent đã bị mất.

Dấu hiệu 2: Onboarding time tăng hơn 50% so với 6 tháng trước dù team size không thay đổi.

Dấu hiệu 3: Bug fix rate cao hơn feature delivery rate trong 2 sprint liên tiếp.

Dấu hiệu 4: Team sợ deploy vào Friday - không phải vì culture, mà vì thực sự không biết code sẽ làm gì trong production.

Khi Nào KHÔNG Rewrite

Đừng rewrite chỉ vì "code xấu". Xấu có thể refactor incrementally. Rewrite chỉ khi architecture không còn fit được business requirements hiện tại, hoặc khi không thể test được.

Nếu quyết định rewrite: lần này viết tay phần core, dùng AI cho boilerplate. Ngược lại với lần đầu. Và document architectural decisions ngay từ đầu, không phải sau.

Kết Luận

Framework quản lý AI tech debt tóm lại ở 4 điểm:

Đo thật: Đừng chỉ đo features shipped. Đo cả bug fix time, onboarding time, và "time to understand" khi có incident.

Own thật: Người merge là owner. Không có ownership chia sẻ với AI model.

Document context: Prompt và business reason phải sống trong PR description hoặc ADR, không chỉ trong chat history.

Refactor có kỷ luật: Characterization tests trước, incremental sau, không big-bang.

AI là công cụ leverage mạnh nhất tôi từng thấy trong 15 năm làm tech. Nhưng leverage khuếch đại cả tốt lẫn xấu. Team dùng AI tốt sẽ deliver nhanh và có codebase healthy. Team dùng AI không có kỷ luật sẽ deliver nhanh trong 6 tháng, rồi mất 18 tháng tiếp theo để trả debt.

Bạn chọn đường nào phụ thuộc vào việc bạn có đang đo đúng không.