VibeTDD Phase 2: AI-led TDD - Cuộc Chiến Với Dịch Vụ Thanh Toán và Khám Phá Giới Hạn Của AI
Lê Lân
1
VibeTDD Giai Đoạn 2: Từ Máy Tính Đơn Giản Đến Dịch Vụ Chi Trả Thực Tế
Mở Đầu
Giai đoạn 2 trong hành trình VibeTDD đánh dấu bước chuyển mình quan trọng: từ việc dạy máy tính thực hiện các phép tính đơn giản sang xây dựng một dịch vụ chi trả thực tế phức tạp hơn nhiều.
Sau khi dự án thử nghiệm máy tính với phương pháp phát triển theo hướng kiểm thử (TDD) do Claude, AI đồng hành của tôi, hướng dẫn đã cho thấy kết quả khả quan, tôi quyết định nâng cấp thử thách lên một mức độ cao hơn. Lần này, thay vì các bài toán “đồ chơi”, tôi đặt ra một bài toán thực tế từ nền tảng Portfo — xây dựng một dịch vụ chi trả với nhiều yêu cầu khắt khe về xác thực dữ liệu, giới hạn số tiền, loại tiền tệ, cũng như lưu trữ và xử lý lỗi một cách tinh tế.
Bài viết này sẽ chia sẻ toàn bộ quá trình thực hiện, những vấn đề thực tế gặp phải khi cộng tác cùng AI, các điểm mạnh – điểm yếu của AI trong việc hỗ trợ TDD, cùng giải pháp “VibeTDD Principle” mà tôi tạo ra để tối ưu hoạt động phát triển phần mềm với AI. Cuối cùng, sẽ là những bài học quý giá rút ra từ trải nghiệm này.
Thách Thức: Từ Đồ Chơi Đến Ứng Dụng Thực Tế
Yêu Cầu Dự Án
Nhiệm vụ đặt ra cho dịch vụ chi trả như sau:
Xác thực dữ liệu chi trả gồm UserId, Amount, Currency
Số tiền chi trả tối đa không vượt quá 30
Chỉ chấp nhận tiền tệ EUR, USD và GBP
Tổng số tiền chi trả cho mỗi người dùng không vượt quá 100
Lưu trữ các khoản chi hợp lệ vào bộ nhớ
Xử lý lỗi xác thực một cách mềm mại, có thông báo chi tiết
Quy Tắc Khi Phát Triển Với AI Claude
Claude dẫn dắt toàn bộ quá trình TDD
Tôi chỉ thực thi theo chỉ dẫn, không tự chủ động viết mã hoặc kiểm thử
Nếu Claude hỏi bước tiếp theo, tôi luôn trả lời: “decide yourself”
Tuy vậy, lần này tôi kỹ càng quan sát các dấu hiệu anti-pattern
Những Vấn Đề Gặp Phải Khi Cộng Tác Với AI
1. Bộc Phát Nhiều Test Quá Đà (Test Explosion)
Claude khởi đầu tốt với một test đơn giản:
@Test
fun `should store payout when all datais valid`() {
val payout = Payout("user123", 25.0, "EUR")
payoutService.processPayout(payout) // Không được lỗi
}
Tuy nhiên, tiếp theo AI gợi ý tới 15 test khác nhau cho chức năng tương tự, kiểm thử mọi tổ hợp nhỏ nhất:
Test với số tiền nhỏ nhất
Test số tiền lớn nhất
Test với từng loại tiền tệ EUR, USD, GBP
Và nhiều biến thể tương tự nữa
Cảnh báo #1: AI có xu hướng kiểm thử đầy đủ từng trường hợp thay vì tập trung vào bộ test đơn giản, đủ bao phủ (triangulation).
Khi Claude bắt đầu tạo trực tiếp nhiều biến thể Payout trong các test, tôi đề xuất áp dụng Object Mother để tái sử dụng dữ liệu test. Nhưng Claude lại tạo một lớp Object Mother với hàng loạt phương thức cho từng giá trị hợp lệ, không hợp lệ một cách rườm rà:
Cảnh báo #2: AI xem Object Mother là nhà máy sản xuất từng biến thể cố định thay vì một phương thức duy nhất linh hoạt, có giá trị mặc định ngẫu nhiên hoặc tùy chỉnh.
Cách đúng phải là:
object PayoutMother {
funof(
userId: String = Rand.string(),
amount: Double = Rand.amount(),
currency: String = Rand.currency()
) = Payout(userId, amount, currency)
}
3. Không Thể Áp Dụng TDD Kinh Điển Với AI
Chu trình đỏ-xanh-tái cấu trúc truyền thống hoàn toàn không khả thi:
Quá Trình Kỳ Vọng
Thực Tế Với AI
Viết 1 test thất bại
AI viết 5-10 test 1 lúc
Viết mã làm test xanh
AI code liền một mạch, bỏ qua bước nhỏ
Tái cấu trúc
Bỏ qua hoặc làm vội vàng
Lặp lại
Không lặp từng bước mà chạy nhanh
Vấn đề sâu xa: TDD kinh điển phụ thuộc vào sự “tương tác nhỏ, lặp lại”, trong khi AI dễ mắc “context explosion”, tiêu tốn bộ nhớ và thời gian phản hồi, cuối cùng không thể giữ tốt ngữ cảnh khi dự án phức tạp.
4. Hướng Dẫn Quá Nhiều và Không Chính Xác
Claude thường tự động chạy code dài hơi mặc dù tôi chưa cho phép. Tôi đã học được cần phải:
Rất cụ thể trong chỉ dẫn
Yêu cầu từng bước nhỏ: “Chỉ viết test cho UserId trống”, “Chỉ thực thi hàm xác thực UserId”
Tránh dùng những câu lệnh chung chung như “tiếp tục”, “implement validation”
5. Thiếu Các Nguyên Tắc Kỹ Thuật Cơ Bản
AI thường bỏ qua hoặc không áp dụng:
Tách biệt các trách nhiệm theo SRP (Single Responsibility Principle)
Sử dụng mocking trong test, thay vào đó dùng luôn các dependency thật
Loại bỏ “magic number”, hardcode trực tiếp
Viết code compile không đúng do sai import hoặc cú pháp
Can Thiệp Để Cứu Chữa Kiến Trúc
Tôi phải dừng Claude khi nó tạo ngay một class xử lý mọi việc hỗn loạn:
classPayoutService {
funprocessPayout(payout: Payout) {
if (payout.userId.isEmpty()) throw Exception("...")
if (payout.amount <= 0) throw Exception("...")
if (payout.currency !in listOf("EUR", "USD", "GBP")) throw Exception("...")
storage.store(payout)
}
}
Tôi nói: “Vi phạm SRP! Phải tách phần xác thực riêng với phần xử lý kinh doanh.”
Claude đồng ý và cùng tôi chia nhỏ:
Mô Hình Dữ Liệu
dataclassPayout(
val userId: String,
val amount: Double,
val currency: String
)
Interface Validator
interfacePayoutValidator {
funvalidate(payout: Payout)
}
Các Validator Riêng Biệt
classUserIdValidator : PayoutValidator {
overridefunvalidate(payout: Payout) {
if (payout.userId.isEmpty()) {
throw InvalidPayoutException(PayoutErrorCode.EMPTY_USER_ID, "UserId cannot be empty")
Tạo ra nhiều test cases đa dạng (đôi khi quá nhiều)
Nhận diện và áp dụng mẫu thiết kế nhất quán
Hỗ trợ tái cấu trúc mã nguồn hiệu quả
⚠️ Cần Giám Sát Nhiều
Quyết định kiến trúc thường sai hoặc quá đơn giản mặc định
Pha trộn các trách nhiệm trong một lớp/tệp
Chiến lược test không hiệu quả: vừa quá thừa vừa không đủ
Quản lý phụ thuộc kém: không dùng mocking, test với thư viện hoặc hệ thống thật
❌ AI Gặp Khó Khăn
Kỷ luật “TDD kinh điển” không khả thi do AI viết nhiều test cùng lúc
Thực hiện luôn toàn bộ giải pháp, bỏ qua giai đoạn viết code minimal
Mất kiểm soát ngữ cảnh khi tính năng phức tạp
Đánh giá chất lượng tệ mà vẫn tự tin
Vấn Đề Mở Rộng Quy Mô
Tính Năng
Số Dòng Mã
Độ Khó với AI
Khả Năng Tự Động Hoá
Máy tính đơn giản
~10
Dễ dàng
Rất cao
Dịch vụ chi trả
~200
Khó khăn, cần can thiệp
Trung bình
Ứng dụng thực tế
> 1000
Hầu như không khả thi
Thấp
AI có một ngưỡng phức tạp bên trên đó hành vi thay đổi, cần con người điều phối và kiểm soát chặt chẽ.
Bài Học Cho VibeTDD
1. Phải Thay Đổi TDD Theo AI
Viết từng bộ test nhỏ tập trung rồi code cả bộ giúp giảm overhead và duy trì kỷ luật test-first.
2. AI Khuếch Đại Phong Cách Của Bạn
Nếu không có khuôn mẫu rõ ràng, AI tự sinh ra phong cách - thường là không hiệu quả hoặc chống bảo trì.
3. Cần Quản Lý Siêu Chi Tiết
Chia nhỏ công việc cực nhỏ, dễ kiểm soát bởi AI vì nó không thể xử lý nhiều ngữ cảnh phức tạp cùng lúc.
4. Kiến Trúc Phải Do Người Dẫn Dắt
AI ưu tiên cấu trúc đơn giản nhất, không phù hợp với phần mềm duy trì lâu dài.
5. Cần Quản Trị Chiến Lược Test
Cưỡng chế AI viết test chiến lược thay vì quá tải test từng trường hợp nhỏ không cần thiết.
Kết Luận
Giai đoạn 2 của VibeTDD là một hành trình vừa thử thách vừa mở mắt. AI Claude cho thấy khả năng triển khai code nhanh chóng, tạo test đa dạng, tái cấu trúc hiệu quả. Nhưng để làm TDD thực sự hiệu quả và tạo ra phần mềm chất lượng, con người vẫn giữ vai trò không thể thay thế trong việc định hướng kỹ thuật, kiến trúc và chiến lược test.
TDD không chỉ là viết test trước mà là cả một quy trình tư duy làm phần mềm có cấu trúc và bảo trì tốt. AI có thể làm mechanics, nhưng không thể hiện thực hóa quá trình tư duy đó.
Tham Khảo
Maksim Matlakhov. VibeTDD Series - Calculator ExperimentLink