Bài viết

AI Vá Code: Vibe Coder Hết Lo? Quy Trình Dùng LLM Sửa Bug Không Tự Bắn Vào Chân

AI có thể vá code rất nhanh, nhưng muốn dùng an toàn thì phải ép nó đi qua test, diff nhỏ, context đủ và review như một junior engineer.

AI Vá Code: Vibe Coder Hết Lo? Quy Trình Dùng LLM Sửa Bug Không Tự Bắn Vào Chân

AI vá code đang rất hấp dẫn — và cũng rất dễ nguy hiểm

“Vibe coding” làm nhiều developer quen với cảm giác mô tả ý tưởng bằng tiếng Việt/tiếng Anh rồi để AI generate code. Nhưng vấn đề thật không nằm ở lúc tạo file mới. Vấn đề bắt đầu khi production bug xuất hiện, test đỏ, log báo lỗi, deadline dí sát, và bạn hỏi AI: “sửa giúp đoạn này”.

Một post gần đây trong cộng đồng AI nhắc tới một thống kê đáng chú ý: có khoảng 38.000 cuộc hội thoại thật mà người dùng hỏi Claude một câu rất trực diện: “tôi nên làm gì?”. Câu chốt đáng nhớ: “Hỏi nó để mở rộng góc nhìn thì tốt; coi nó như tiếng nói cuối cùng thì nguy hiểm.”

Điều này đúng gần như tuyệt đối với code. AI có thể đọc stack trace, đề xuất patch, viết test, refactor nhanh hơn người. Nhưng nếu bạn để nó merge thẳng vào codebase mà không có guardrail, bạn không đang “tăng tốc engineering” — bạn đang outsource judgment cho một hệ thống không chịu pager lúc 2 giờ sáng.

AI vá code thực sự hoạt động như thế nào?

Khi bạn đưa bug report, stack trace và một phần code cho LLM như Claude, GPT hoặc Gemini, nó không “debug” giống con người theo nghĩa chạy chương trình, inspect memory, attach debugger. Nó dự đoán đoạn sửa hợp lý dựa trên pattern đã học và context bạn cung cấp.

Một vòng sửa bug bằng AI thường gồm 4 bước:

  1. Hiểu lỗi từ mô tả và log
  2. Suy luận nguyên nhân khả dĩ
  3. Đề xuất patch
  4. Viết hoặc cập nhật test để chứng minh patch đúng

Điểm mạnh của LLM là tốc độ đọc và tổng hợp context. Ví dụ bạn paste vào một stack trace Node.js:

TypeError: Cannot read properties of undefined (reading 'id')
    at createOrder (/app/src/services/order.service.ts:42:31)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

Kèm đoạn code:

1
2
3
4
5
6
7
8
9
10
11
export async function createOrder(input: CreateOrderInput) {
  const user = await userRepository.findByEmail(input.email);

  const order = await orderRepository.create({
    userId: user.id,
    amount: input.amount,
    status: 'pending',
  });

  return order;
}

AI sẽ rất nhanh nhận ra user có thể là undefined/null. Patch “có vẻ đúng” thường là:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export async function createOrder(input: CreateOrderInput) {
  const user = await userRepository.findByEmail(input.email);

  if (!user) {
    throw new UserNotFoundError(`User not found for email: ${input.email}`);
  }

  const order = await orderRepository.create({
    userId: user.id,
    amount: input.amount,
    status: 'pending',
  });

  return order;
}

Nhưng đây mới là phần dễ. Câu hỏi engineering thật là:

  • API hiện tại nên trả 404, 400, hay tự tạo user mới?
  • Có nên leak email trong error message không?
  • UserNotFoundError đã tồn tại chưa?
  • Caller có catch loại error này không?
  • Có test nào cover case user không tồn tại chưa?
  • Thay đổi này có phá backward compatibility không?

LLM thường giỏi đưa ra patch syntactically plausible — tức nhìn hợp lý, compile được, giống code thật. Nhưng “hợp lý” không đồng nghĩa “đúng với business rule”.

Vì vậy, cách dùng AI vá code an toàn không phải là “hãy sửa bug này”, mà là ép nó làm việc trong một workflow có kiểm soát.

Một prompt thực tế nên có dạng như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Bạn là senior backend engineer review một bug trong codebase TypeScript/NestJS.

Mục tiêu:
- Tìm nguyên nhân gốc của lỗi.
- Đề xuất patch nhỏ nhất có thể.
- Không refactor ngoài phạm vi bug.
- Nếu thiếu context, hãy nêu giả định rõ ràng.
- Bắt buộc viết test chứng minh bug đã được sửa.

Thông tin:
- Stack trace:
[paste stack trace]

- File liên quan:
[paste file hoặc diff]

- Business rule:
[paste rule nếu có]

Output yêu cầu:
1. Root cause ngắn gọn.
2. Patch dạng unified diff.
3. Test case cần thêm.
4. Rủi ro còn lại sau patch.
5. Những câu hỏi cần confirm với team nếu business rule chưa rõ.

Điểm quan trọng là bạn không yêu cầu AI “code hộ”. Bạn yêu cầu nó tạo artifact để review: root cause, diff, test, rủi ro.

Ví dụ output bạn nên bắt AI trả về dạng diff:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
diff --git a/src/services/order.service.ts b/src/services/order.service.ts
index 7c4a1aa..2d91e0b 100644
--- a/src/services/order.service.ts
+++ b/src/services/order.service.ts
@@ -1,8 +1,13 @@
 export async function createOrder(input: CreateOrderInput) {
   const user = await userRepository.findByEmail(input.email);
 
+  if (!user) {
+    throw new UserNotFoundError('User not found');
+  }
+
   const order = await orderRepository.create({
     userId: user.id,
     amount: input.amount,
     status: 'pending',
   });

Và test:

1
2
3
4
5
6
7
8
9
it('throws UserNotFoundError when user does not exist', async () => {
  userRepository.findByEmail.mockResolvedValue(null);

  await expect(
    createOrder({ email: '[email protected]', amount: 1000 })
  ).rejects.toThrow(UserNotFoundError);

  expect(orderRepository.create).not.toHaveBeenCalled();
});

Nếu AI chỉ trả lời bằng đoạn code cuối cùng mà không có test và rủi ro, bạn đang nhận “lời khuyên”, không phải một patch đủ tiêu chuẩn merge.

Khác biệt giữa AI vá code và autocomplete truyền thống

Autocomplete kiểu cũ như IDE suggestion hoặc snippet chủ yếu hoạt động ở phạm vi dòng/hàm. AI coding assistant hiện đại có thể xử lý phạm vi lớn hơn: nhiều file, stack trace, issue description, test output, migration, config CI/CD.

Nhưng phạm vi lớn hơn cũng nghĩa là lỗi tinh vi hơn.

Một autocomplete sai thường gây compile error. Một patch AI sai có thể:

  • Nuốt exception làm hệ thống “xanh” giả.
  • Thêm retry gây double charge.
  • Sửa query PostgreSQL làm mất index usage.
  • Đổi validation khiến API accept dữ liệu bẩn.
  • Thêm fallback làm che mất lỗi upstream.
  • Viết test mock quá mức nên test pass nhưng production vẫn fail.

Ví dụ nguy hiểm:

1
2
3
4
5
6
try {
  return await paymentGateway.charge(payload);
} catch (err) {
  logger.error(err);
  return { status: 'success' };
}

Patch này có thể làm test pass nếu test chỉ assert API không throw. Nhưng với domain payment, đây là lỗi nghiêm trọng. AI không tự biết “không được trả success khi charge fail” nếu bạn không đưa business invariant.

Do đó, nguyên tắc là: đưa invariant vào prompt.

Ví dụ:

1
2
3
4
Business invariants:
- Không bao giờ trả status=success nếu payment gateway charge thất bại.
- Không retry payment nếu request không có idempotency key.
- Mọi lỗi payment phải được ghi vào payment_attempts với trạng thái failed.

LLM càng có context cụ thể, xác suất patch đúng càng cao. Nhưng vẫn phải có test và review.

Workflow 6 bước để dùng AI sửa bug ngay trong team

Một workflow thực dụng cho backend team:

Bước 1: Tạo reproduction trước

Trước khi hỏi AI sửa, hãy có test fail hoặc lệnh reproduce.

1
pnpm test order.service.spec.ts --runInBand

Nếu chưa có test, yêu cầu AI viết test fail trước:

1
Dựa trên bug report này, hãy viết test case tái hiện lỗi. Chưa sửa implementation.

Đây là cách chặn hallucination tốt nhất. Nếu không reproduce được, AI rất dễ sửa “triệu chứng tưởng tượng”.

Bước 2: Cung cấp context tối thiểu nhưng đủ

Đừng paste cả repo. Hãy đưa:

  • Stack trace
  • File chứa function lỗi
  • Interface/type liên quan
  • Test hiện tại
  • Business rule
  • Version framework nếu có liên quan

Ví dụ: Node.js 20, NestJS 10, Prisma 5, PostgreSQL 15.

Bước 3: Bắt AI đề xuất nhiều giả thuyết

Thay vì hỏi “sửa đi”, hãy hỏi:

1
Liệt kê 3 nguyên nhân khả dĩ nhất, cách xác minh từng nguyên nhân, rồi chọn patch nhỏ nhất.

Cách này giúp bạn dùng AI để mở rộng góc nhìn, đúng như câu chốt trong nguồn: dùng AI như người phản biện, không phải tiếng nói cuối cùng.

Bước 4: Chỉ nhận unified diff

Không copy-paste đoạn file nguyên khối. Diff giúp review được scope.

1
Chỉ output unified diff. Không đổi format code ngoài vùng cần sửa.

Bước 5: Chạy test, lint, typecheck

Tối thiểu:

1
2
3
pnpm lint
pnpm typecheck
pnpm test

Với backend có database:

1
pnpm test:integration

Nếu patch chạm SQL hoặc migration, cần kiểm tra query plan:

1
2
3
4
5
6
EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM orders
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT 20;

AI có thể đề xuất query “đúng kết quả” nhưng phá performance do không dùng index.

Bước 6: Review như review code của junior engineer

Bạn không cần khinh thường AI, nhưng nên review nó như một junior rất nhanh tay:

  • Có hiểu domain không?
  • Có sửa quá phạm vi không?
  • Có thêm behavior ngầm không?
  • Test có thật sự bắt bug không?
  • Có case concurrency/idempotency/security không?
  • Có log nhạy cảm không?

Use case thực tế: đội backend dùng AI để giảm thời gian xử lý bug

Một use case phù hợp nhất là bug loại “có stack trace rõ, phạm vi hẹp, có test hoặc dễ viết test”. Ví dụ:

  • Null/undefined edge case
  • Sai mapping DTO/entity
  • Validation thiếu case
  • Query thiếu điều kiện
  • Race condition đơn giản đã có log
  • Test flaky do mock time hoặc async handling

Giả sử team có service xử lý đơn hàng. Bug: một số request tạo order bị lỗi 500 khi email chưa tồn tại trong hệ thống. Trước đây developer mất 30-45 phút để đọc log, tìm service, viết test, sửa và chạy lại CI. Với AI workflow tốt, quá trình có thể rút xuống 10-15 phút:

  1. Paste stack trace, service code, repository interface.
  2. Yêu cầu AI viết test fail.
  3. Chạy test xác nhận fail.
  4. Yêu cầu patch nhỏ nhất.
  5. Apply diff.
  6. Chạy test/lint/typecheck.
  7. Review error contract với API layer.

Kết quả tốt không phải là “AI thay developer”. Kết quả tốt là developer không mất thời gian ở phần cơ học: đọc boilerplate, nhớ cú pháp mock, viết test khung, sửa import. Thời gian còn lại dành cho phần quan trọng hơn: quyết định behavior đúng.

Ngược lại, các bug không nên giao thẳng cho AI:

  • Thay đổi logic payment, billing, permission.
  • Migration dữ liệu lớn.
  • Bug performance không có profiling.
  • Security issue liên quan auth/session/token.
  • Incident production chưa rõ blast radius.
  • Code có side effect phức tạp qua queue, cron, event bus.

Với các case này, AI vẫn hữu ích để tạo checklist, gợi ý hướng điều tra, viết script kiểm tra dữ liệu. Nhưng không nên để nó tự quyết patch.

Kết luận: Vibe coder chưa hết lo, nhưng có thể bớt lo nếu làm đúng

Takeaway cụ thể:

  1. Đừng hỏi AI “sửa code giúp tôi”. Hãy yêu cầu root cause, unified diff, test case và rủi ro còn lại.
  2. Luôn có reproduction hoặc test fail trước patch. Không có test, AI rất dễ tạo patch nhìn hợp lý nhưng sai domain.
  3. Dùng AI để mở rộng góc nhìn, không dùng làm người quyết định cuối cùng. Người chịu trách nhiệm vẫn là engineer merge code.

AI vá code là công cụ mạnh. Nhưng trong engineering, tốc độ chỉ có giá trị khi đi kèm kiểm chứng. Nếu patch không có test, không có diff nhỏ, không có review domain, thì đó không phải automation — đó là đánh bạc bằng syntax đẹp.

Bài viết này được cấp phép bởi tác giả theo giấy phép CC BY 4.0 .