Mở đầu — vì sao bài này quan trọng
Nếu bạn từng mở một cửa sổ chat với AI, gõ "viết cho tôi hàm login bằng Python" rồi nhận về một đoạn code trông rất đẹp nhưng chạy là văng lỗi, hoặc bảo mật lỏng lẻo đến mức không dám đưa lên production — thì bạn đã trải nghiệm đúng cái bẫy lớn nhất của việc dùng AI trong kỹ thuật phần mềm. Vấn đề gần như không bao giờ nằm ở chỗ "AI dốt". Vấn đề nằm ở chỗ bạn hỏi quá mơ hồ, và một câu hỏi mơ hồ thì luôn nhận về một câu trả lời mơ hồ, dù người trả lời là con người hay là mô hình.
Kỹ thuật (engineering) là một trong ba lĩnh vực mà prompt engineering tạo ra đòn bẩy lớn nhất, đơn giản vì code có tính đúng/sai rõ ràng: nó chạy hoặc không chạy, nó pass test hoặc fail test. Không có vùng xám kiểu "bài viết này hay hay dở". Chính sự rõ ràng đó khiến prompt tốt cho lập trình vừa dễ đo lường hiệu quả, vừa dễ tạo ra khác biệt về năng suất. Một lập trình viên biết viết prompt cho ba tình huống cốt lõi — sinh code (code generation), review code, và gỡ lỗi (debug) — có thể làm nhanh gấp 2–3 lần một người cùng trình độ nhưng chỉ biết "copy đề bài dán vào chat".
Trong bài này, chúng ta tập trung đúng vào ba tình huống đó. Không nói về workflow nhiều bước (bài 10, 58 lo), không nói về function calling (bài 24 lo), không nói về SQL/data (bài 51 lo). Chỉ nói về việc: làm sao ra lệnh cho AI viết code, soi code, và sửa code — sao cho kết quả đủ tốt để đưa vào dự án thật.
Khái niệm cốt lõi
Ba tác vụ engineering có ba "hình dạng prompt" khác nhau. Hiểu được cấu trúc riêng của từng loại là chìa khóa.
1. Code generation — sinh code từ mô tả
Sai lầm phổ biến nhất là mô tả tác vụ mà bỏ quên phần "hợp đồng" (contract) của hàm. Một prompt sinh code tốt phải nêu rõ năm thành phần:
- Task: hàm/module này làm gì, bằng ngôn ngữ nào.
- Input: kiểu dữ liệu đầu vào, ràng buộc (ví dụ: chuỗi không rỗng, số dương).
- Output: kiểu trả về, định dạng.
- Edge cases: các trường hợp biên phải xử lý — input rỗng, null, số âm, ký tự Unicode, tràn số.
- Constraints: ngôn ngữ/framework, thư viện được phép dùng, phong cách code, có cần test kèm không.
Viết một hàm [ngôn ngữ] để [tác vụ].Yêu cầu:
- Input: [kiểu và ràng buộc]
- Output: [kiểu và định dạng]
- Edge cases cần xử lý: [danh sách]
- Ràng buộc: [framework, thư viện, style, error handling]
- Kèm: [unit test / docstring / type hints]
Giải thích ngắn gọn các quyết định thiết kế quan trọng.
Điểm mấu chốt: chính bạn — chứ không phải AI — phải là người nghĩ ra danh sách edge case. AI rất giỏi viết code cho những gì bạn nêu rõ, nhưng nó không đọc được suy nghĩ. Nếu bạn không nói "xử lý trường hợp danh sách rỗng", nhiều khả năng nó sẽ quên.
2. Code review — nhờ AI soi code
Review khác generation ở chỗ AI đã có sẵn code, nhiệm vụ là tìm vấn đề. Prompt review dở là "review giúp tôi đoạn này" — quá rộng, AI sẽ trả về một mớ nhận xét chung chung. Prompt review tốt phải định nghĩa tiêu chí review (rubric) và yêu cầu định dạng đầu ra có mức độ nghiêm trọng.
Các trục nên yêu cầu AI soi: đúng đắn logic (correctness), lỗ hổng bảo mật (security), hiệu năng (performance), khả năng đọc/bảo trì (readability), và tuân thủ convention của dự án. Yêu cầu AI phân loại mỗi phát hiện theo mức Critical / Major / Minor, và với mỗi lỗi phải chỉ rõ dòng nào, tại sao sai, và cách sửa gợi ý. Cách này biến "một đống ý kiến" thành "một checklist hành động được".
3. Debug — gỡ lỗi
Debug là nơi kỹ thuật prompt gặp Chain-of-Thought (bạn đã học ở bài 7). Đừng hỏi "code này sao lỗi, sửa giúp tôi". Hãy cung cấp đủ ba mảnh: code liên quan, thông báo lỗi/stack trace đầy đủ, và hành vi mong đợi so với hành vi thực tế. Sau đó yêu cầu AI: (1) đưa ra các giả thuyết về nguyên nhân, (2) chỉ ra nguyên nhân có khả năng nhất kèm lý do, (3) mới đề xuất sửa. Bắt AI "suy nghĩ trước khi sửa" giúp tránh việc nó vá triệu chứng thay vì chữa gốc bệnh.
Nguyên tắc xuyên suốt: cung cấp ngữ cảnh (context)
Cả ba tác vụ đều cải thiện vọt khi bạn đưa ngữ cảnh: phiên bản ngôn ngữ/framework, quy ước code của team, code xung quanh mà hàm sẽ tích hợp vào. AI không biết dự án của bạn dùng React 18 hay 16, dùng Prettier hay không, đặt tên biến camelCase hay snake_case — trừ khi bạn nói.
Tình huống thực tế
Ví dụ 1 — Tiki: sinh code cho hàm tính phí ship
Một kỹ sư backend tại một sàn thương mại điện tử kiểu Tiki cần một hàm tính phí vận chuyển. Lần đầu anh gõ: "Viết hàm Python tính phí ship theo cân nặng." Kết quả là một hàm 5 dòng, nhân cân nặng với một hằng số — vô dụng với nghiệp vụ thật.
Anh viết lại prompt theo khung năm thành phần:
Viết hàm Python tinh_phi_ship(can_nang_kg, tinh_thanh, la_hang_cong_kenh).Yêu cầu:
- Input: can_nang_kg (float > 0), tinh_thanh (str, thuộc 63 tỉnh thành),
la_hang_cong_kenh (bool)
- Output: int (phí VND, làm tròn đến 1000đ)
- Logic: nội thành HCM/HN 22.000đ cho 0.5kg đầu, mỗi 0.5kg thêm +5.000đ;
tỉnh khác nhân hệ số 1.5; hàng cồng kềnh cộng thêm 30%
- Edge cases: can_nang_kg <= 0 raise ValueError; tinh_thanh không hợp lệ
raise ValueError; cân nặng lẻ làm tròn lên bội số 0.5kg
- Kèm type hints, docstring tiếng Việt, và 5 unit test dùng pytest
Lần này AI trả về hàm xử lý đúng cả trường hợp làm tròn cân nặng lẻ và raise lỗi hợp lý, kèm test bao phủ các nhánh. Anh chỉ mất 3 phút chỉnh lại bảng hệ số theo giá thật.
Bài học: thời gian bạn bỏ ra để viết rõ input/output/edge case chính là thời gian bạn tiết kiệm được ở khâu sửa lỗi. Prompt càng cụ thể, code càng gần production.
Ví dụ 2 — Một fintech ở Singapore: review một pull request nhạy cảm
Đội kỹ thuật của một fintech nhỏ tại Singapore dùng AI làm "vòng review đầu tiên" trước khi người review thật vào cuộc, để bắt lỗi lộ liễu và tiết kiệm thời gian reviewer con người. Với một PR xử lý giao dịch nạp tiền, họ dùng prompt review có rubric:
Đóng vai senior engineer review đoạn code xử lý nạp tiền dưới đây.
Rà theo đúng các trục sau, mỗi phát hiện gán mức [Critical/Major/Minor]:- Correctness: race condition, tính tiền sai, làm tròn số dư
- Security: SQL injection, thiếu kiểm tra quyền, log lộ thông tin nhạy cảm
- Idempotency: request bị gửi 2 lần có bị cộng tiền 2 lần không
- Performance: query N+1, thiếu index
Với mỗi lỗi: chỉ số dòng, giải thích tại sao nguy hiểm, đề xuất cách sửa.
Nếu không tìm thấy lỗi ở một trục, ghi rõ "Không phát hiện".[dán code]
AI phát hiện một lỗi Critical: hàm cộng số dư không có khóa (lock), nên hai request đồng thời có thể gây double-credit — đúng loại lỗi mà mắt người dễ bỏ sót khi đọc lướt. Reviewer con người sau đó xác nhận và team thêm optimistic locking.
Bài học: định nghĩa rubric và bắt AI phân loại mức nghiêm trọng biến review từ "cảm tính" thành "có hệ thống". Nhưng lưu ý — AI là vòng đầu, không thay thế người review cuối. Với code động đến tiền, con người luôn phải ký duyệt.
Ví dụ 3 — Startup edtech Việt Nam: debug lỗi chỉ xảy ra trên production
Một startup edtech ở TP.HCM gặp lỗi: API trả video bài học thỉnh thoảng timeout, nhưng chỉ trên production, không tái hiện được ở local. Lập trình viên ban đầu hỏi AI: "API tôi bị timeout, sửa sao?" — và nhận về những gợi ý chung chung vô dụng (tăng timeout, thêm cache...).
Anh viết lại, cung cấp đủ ngữ cảnh và bắt AI suy luận theo giả thuyết:
API endpoint /lessons/{id}/video thỉnh thoảng timeout (>30s) trên production,
không tái hiện được ở local. Stack: Node.js 18, Express, PostgreSQL, S3.Hành vi mong đợi: trả URL video trong <500ms.
Hành vi thực tế: ~2% request timeout, tập trung vào giờ cao điểm 20h-22h.
[dán code handler + đoạn query]
Hãy: (1) liệt kê các giả thuyết nguyên nhân, xếp theo xác suất;
(2) với giả thuyết cao nhất, giải thích cơ chế gây lỗi;
(3) đề xuất cách xác nhận giả thuyết trước khi sửa; (4) rồi mới đề xuất fix.
AI nêu giả thuyết hàng đầu: connection pool của PostgreSQL cạn kiệt vào giờ cao điểm do một query trong handler không đóng connection đúng cách, gây rò rỉ. Nó đề xuất cách xác nhận bằng cách theo dõi metric pg_stat_activity. Team kiểm tra, đúng như dự đoán, và fix bằng cách sửa lại việc release connection.
Bài học: chi tiết "chỉ xảy ra trên production, giờ cao điểm, 2%" chính là manh mối vàng. Debug tốt là cung cấp bằng chứng, không chỉ triệu chứng — và bắt AI đưa giả thuyết trước khi vá.
Hướng dẫn từng bước
Đây là quy trình bạn có thể áp dụng ngay cho cả ba tác vụ.
Bước 1 — Xác định bạn đang ở tác vụ nào. Generation (chưa có code), review (có code, tìm lỗi), hay debug (có code, có lỗi cụ thể). Mỗi loại dùng khung prompt riêng như phần trên.
Bước 2 — Nạp ngữ cảnh trước khi ra lệnh. Nêu ngôn ngữ + phiên bản, framework, convention của team, và dán code liên quan xung quanh nếu có. Đừng để AI đoán.
Bước 3 — Viết "hợp đồng" cho code. Với generation: input, output, edge cases, constraints. Với debug: hành vi mong đợi vs thực tế + stack trace đầy đủ.
Bước 4 — Chỉ định định dạng đầu ra. "Kèm unit test pytest", "phân loại lỗi theo Critical/Major/Minor", "giải thích quyết định thiết kế". Định dạng rõ ràng giúp bạn dùng được kết quả ngay.
Bước 5 — Yêu cầu suy luận trước khi kết luận (nhất là với debug và review). "Liệt kê giả thuyết trước, rồi mới sửa."
Bước 6 — Luôn tự kiểm chứng. Chạy code, chạy test, đọc lại từng dòng. AI có thể "bịa" ra API không tồn tại (hallucination). Bạn là người chịu trách nhiệm cuối, không phải AI.
Bước 7 — Lặp lại có định hướng. Nếu kết quả chưa đạt, đừng gõ lại từ đầu. Chỉ ra cụ thể chỗ sai: "Hàm này chưa xử lý trường hợp danh sách rỗng, thêm vào" — AI sẽ vá đúng chỗ.
Lỗi thường gặp & mẹo
Lỗi 1 — Prompt quá mơ hồ. "Viết hàm xử lý user" là công thức đảm bảo nhận về code vô dụng. Luôn nêu hợp đồng đầy đủ.
Lỗi 2 — Quên nêu edge case. AI viết đúng những gì bạn nói. Không nói "xử lý null" thì đừng ngạc nhiên khi nó không xử lý. Hãy tự liệt kê edge case — đó là phần việc của kỹ sư, không thể khoán cho AI.
Lỗi 3 — Tin code mà không chạy thử. Code trông đúng chưa chắc chạy đúng. AI có thể gọi hàm không tồn tại, import sai thư viện. Luôn chạy và test.
Lỗi 4 — Dán stack trace cụt. Khi debug, dán nửa dòng lỗi thì AI phải đoán. Dán trọn vẹn stack trace + phiên bản môi trường.
Lỗi 5 — Để AI tự chọn thư viện. Nếu không ràng buộc, AI có thể chọn một thư viện lỗi thời hoặc không phù hợp dự án. Hãy nói rõ được dùng gì.
Mẹo — Dùng role prompting (bài 13): "Đóng vai senior security engineer" giúp AI review sắc hơn ở góc bảo mật.
Mẹo — Chia nhỏ. Với module lớn, đừng bắt sinh cả file. Sinh từng hàm, review từng phần — dễ kiểm soát chất lượng hơn nhiều.
Mẹo — Cẩn trọng với code nhạy cảm. Đừng dán API key, mật khẩu, dữ liệu khách hàng thật vào prompt. Thay bằng placeholder.
Bài tập thực hành
- Generation: Chọn một hàm bạn cần viết trong công việc thật. Viết prompt theo khung năm thành phần (task, input, output, edge cases, constraints), yêu cầu kèm unit test. So sánh với prompt "một dòng" bạn hay dùng.
- Review: Lấy một đoạn code bạn viết tuần trước. Viết prompt review có rubric bốn trục (correctness, security, performance, readability) và bắt AI phân loại Critical/Major/Minor. Đếm xem nó tìm ra bao nhiêu lỗi bạn chưa thấy.
- Debug: Nhớ lại một bug gần nhất bạn từng sửa. Viết prompt debug đầy đủ (code + hành vi mong đợi vs thực tế + stack trace) và bắt AI liệt kê giả thuyết trước khi sửa. Đối chiếu giả thuyết của nó với nguyên nhân thật.
Tóm tắt
Ba tác vụ engineering — generation, review, debug — mỗi loại có một khung prompt riêng, nhưng cùng chung một triết lý: AI làm tốt đúng những gì bạn mô tả rõ, và bạn là người chịu trách nhiệm cuối cùng về chất lượng code.
- Generation: nêu đủ hợp đồng (task, input, output, edge cases, constraints) và yêu cầu test kèm.
- Review: định nghĩa rubric và bắt phân loại mức nghiêm trọng để có checklist hành động.
- Debug: cung cấp bằng chứng (code + hành vi mong đợi vs thực tế + stack trace) và bắt AI đưa giả thuyết trước khi sửa.