Kết hợp TDD(Test-Driven Development) và LLM
- TDD là một phương pháp phát triển trong đó trước khi bắt đầu viết chương trình, người ta viết trước các bài kiểm thử đơn vị toàn diện
- Vì các bài kiểm thử trên thực tế đóng vai trò như đặc tả, nên khi tất cả kiểm thử cuối cùng đều vượt qua, có thể phần nào chứng minh được tính đúng đắn của mã
- Theo truyền thống, TDD đôi khi bị chỉ trích là làm giảm năng suất hoặc kém hiệu quả
- Tuy nhiên, với sự xuất hiện của LLM, quá trình viết kiểm thử và lặp đi lặp lại việc chỉnh sửa mã đã trở nên dễ dàng hơn rất nhiều
Cách tôi thường dùng LLM
- Tôi đã tích cực sử dụng các công cụ như Github Copilot
- LLM rất giỏi trong việc tìm ra các mẫu lặp lại và tự động hoàn thành vài dòng tiếp theo, nhưng thường gặp khó khi phải hiểu sâu toàn bộ vấn đề để tạo ra một mô-đun hoàn chỉnh chỉ trong một lần
- Nếu cung cấp quá nhiều ngữ cảnh cần cho việc giải quyết vấn đề, mô hình dễ bị lệch khỏi chủ đề
- Khi tiến hành công việc bằng cách chỉ cung cấp từng phần thông tin theo nhu cầu (chẳng hạn đầu ra lỗi), mô hình cũng hỗ trợ gỡ lỗi rất tốt
- Tôi cảm nhận rõ có ma sát trong quá trình lặp đi lặp lại việc sao chép-dán giữa IDE, terminal và giao diện chat
Có thể tự động hóa không?
- Để tự động hóa quy trình này, tôi đã tự đưa vào khái niệm event loop
- Nếu ở prompt đầu tiên nêu rõ đặc tả và function signature của hàm cần triển khai, mô hình sẽ đưa ra bản nháp của kiểm thử đơn vị và mã
- Mã này được lưu vào thư mục
sandbox và tự động chạy go test
- Nếu kiểm thử thất bại, ở prompt thứ hai (lặp), mã hiện có và kết quả kiểm thử (lỗi biên dịch hoặc thông tin thất bại) sẽ được gửi kèm
- Dựa trên đó, mô hình lại đề xuất kiểm thử và mã triển khai đã được chỉnh sửa
- Quy trình này được lặp lại cho đến khi tất cả kiểm thử đều vượt qua
- Cách tiếp cận này cho phép cải thiện dần dần mà không cần tích lũy quá nhiều ngữ cảnh
- Mô hình có thể lặp đi lặp lại việc thất bại ở cùng một test case; khi đó con người sẽ trực tiếp chỉ ra phần có vấn đề và cung cấp gợi ý
- Cần nhận thức về vấn đề “thiếu người giám sát”, tức phải nghi ngờ liệu các bài kiểm thử do LLM tạo ra có đủ nghiêm ngặt hay không
- Có khả năng mã và kiểm thử cùng chia sẻ một lỗi giống nhau hoặc một thiết kế chưa hoàn chỉnh
- Vì vậy, quá trình con người bổ sung và tăng cường thêm các test case là rất quan trọng
- Nếu cần, cũng có thể thử nghiệm với AI các kỹ thuật như mutation testing
Phát triển dựa trên LLM và tải nhận thức(cognitive load)
- Khi áp dụng TDD cùng với LLM, có thể kỳ vọng cách này không chỉ dùng được cho các bài toán thuật toán thông thường mà còn cho cả codebase thực tế có phụ thuộc
- Tuy nhiên, cần chia cấu trúc dự án thành các đơn vị nhỏ hơn để tăng khả năng bảo trì, và mỗi thư mục/package phải có thể được kiểm thử độc lập
- Khuyến nghị tách mỗi package thành các định nghĩa kiểu chính (
shared.go), các tệp đảm nhiệm logic cụ thể (x.go) và kiểm thử (x_test.go) để giảm tải nhận thức
- Trong quá trình tận dụng AI, thay vì cung cấp toàn bộ mã cho mô hình mỗi lần, hãy chỉ chọn lọc một số phần nhất định để mô hình tập trung
- Điều này giúp tăng độ bao phủ kiểm thử đồng thời giảm mức độ kết dính giữa các mô-đun, từ đó cũng có lợi cho bảo trì dài hạn
- Dù là dự án lớn, vẫn nên hướng tới cấu trúc chia thành các đơn vị nhỏ và rõ ràng, chứa phong phú logic của từng đơn vị nhưng giữ phạm vi ở mức tối thiểu
Kết luận
- Xét đến tốc độ phát triển của AI, rất có thể ngay ngày mai sẽ xuất hiện một kiến trúc mới vượt qua các giới hạn của LLM
- Vì vậy, thay vì đột ngột refactor một lượng lớn mã legacy quy mô hơn 100 nghìn dòng, tôi khuyên nên bắt đầu từ quy mô nhỏ để khám phá khả năng kết hợp giữa TDD và LLM
- Kỳ vọng rằng sự kết hợp giữa TDD và LLM có thể tạo ra những thay đổi tích cực cho cả tự động sinh mã lẫn quản lý chất lượng kiểm thử
5 bình luận
Điều này khiến tôi phải ngẫm xem các dịch vụ AI chuyên cho lập trình khác đang dùng pipeline như thế nào.
(Cứ nhìn mấy thứ này) chẳng mấy chốc có cảm giác như người ta sẽ cấy cả dây kích thích điện vào não vì điện não vậy.
Việc thêm mã kiểm thử có vẻ là điều tốt, nhưng chương trình mà người này tạo ra thì có vẻ không có nhiều giá trị.
Ngay cả trong cline hay aider cũng có thể chạy lệnh dòng lệnh và nhận kết quả, nên nếu chỉ cần viết prompt tốt trong chương trình đó thì xét đến các tiện ích khác, có lẽ dùng cách kia sẽ tốt hơn.
Micro-agent do Builder IO tạo ra cũng là một cách tiếp cận tương tự. https://github.com/BuilderIO/micro-agent Tôi cũng đã thử nhiều lần với LLM và TDD, và cần phải làm tốt việc trừu tượng hóa bằng những thứ như design system. Các convention và pattern cũng phải được thiết lập bài bản. Còn test case thì tôi thường tự viết. (Dù chỉ bằng ngôn ngữ tự nhiên của con người?) Trên hết, đúng như bài viết này nói, phải thiết kế tốt các mô-đun có độ kết dính thấp và tính cố kết cao thì mới có thể nhồi ngữ cảnh vào cửa sổ ngữ cảnh hữu hạn được.
LLM xử lý khá tốt các đoạn mã nhỏ, nhưng ở phần thiết kế tổng thể và tầm nhìn lớn thì vẫn còn hơi hạn chế.
Có vẻ cách kết hợp với TDD để cải thiện dần từng chút một là khá hay.