Hai năm trước, cách mình dùng AI để code là gõ đại một câu kiểu "write a function to validate email" rồi hy vọng kết quả dùng được. Đôi khi được, đôi khi code ra đầy bug, đôi khi AI hiểu sai hoàn toàn ý mình. Mình cứ nghĩ AI chưa đủ giỏi.
Nhưng thực tế là: cùng một model, cùng một câu hỏi, chỉ thay đổi cách hỏi — kết quả có thể khác nhau từ trời vực. Prompt engineering không phải kỹ năng dành cho "AI researcher" hay "prompt specialist" — nó là kỹ năng giao tiếp với công cụ mà developer sẽ dùng mỗi ngày trong nhiều năm tới.
Bài viết này mình viết từ góc nhìn developer, tập trung vào những kỹ thuật thực sự giúp bạn code hiệu quả hơn với AI — không phải lý thuyết suông, mà là pattern cụ thể bạn có thể áp dụng ngay.
Tại sao developer cần prompt engineering?
Bạn có thể nghĩ: "Mình là developer, không phải content creator. Cần gì prompt engineering?" Mình cũng từng nghĩ vậy — cho đến khi nhận ra mình đang mất 30 phút chỉnh sửa code AI sinh ra, trong khi đồng nghiệp cùng dùng một tool nhưng nhận được code gần như production-ready ngay lần đầu.
Sự khác biệt không nằm ở tool. Nằm ở cách hỏi.
Với developer, prompt engineering quan trọng vì ba lý do. Một là tiết kiệm token và thời gian — prompt tốt cho kết quả đúng ngay lần đầu, không cần 5-6 vòng sửa đi sửa lại. Hai là kiểm soát output — bạn muốn code theo convention của team, theo architecture cụ thể, dùng pattern cụ thể — prompt đúng cách giúp AI hiểu những ràng buộc này. Ba là tránh hallucination — prompt càng rõ ràng, AI càng ít bịa.
Level 0: Cách hầu hết developer đang prompt
Hãy bắt đầu bằng cách nhìn thẳng vào vấn đề. Đây là cách mình — và có lẽ nhiều bạn — đang hỏi AI:
Viết API tạo invoiceKết quả: AI sinh ra một đống code dùng framework nó tự chọn (có thể Express.js trong khi bạn dùng .NET), convention nó tự đặt, không validation, không error handling, không match với codebase hiện có. Bạn mất 30 phút refactor — lúc đó tự viết có khi nhanh hơn.
Vấn đề không phải AI kém. Vấn đề là bạn đưa quá ít context. AI không biết bạn dùng tech stack gì, project structure ra sao, convention thế nào, hay trường hợp edge case nào cần xử lý. Nó phải đoán — và đoán thì hay sai.
Level 1: Cung cấp context — Kỹ thuật quan trọng nhất
Nếu chỉ được chọn một kỹ thuật duy nhất, mình chọn cái này: cho AI biết context.
Nói rõ tech stack và convention
Viết API endpoint tạo invoice trong ASP.NET Core 8 Web API.
- Dùng MediatR pattern (Command + Handler)
- Entity: Invoice (Id, InvoiceNumber, CustomerId, TotalAmount,
Status, CreatedAt, TenantId)
- Validation bằng FluentValidation
- Return CreatedAtAction với InvoiceDto
- Tất cả entity kế thừa BaseEntity có CreatedAt, UpdatedAt, IsDeleted
- Multi-tenant: TenantId lấy từ JWT claimSo sánh hai kết quả:
Prompt mơ hồ → AI đoán stack, đoán pattern, code không dùng được. Prompt có context → AI sinh code đúng stack, đúng pattern, gần production-ready.
Cho AI thấy code hiện có
Đây là kỹ thuật hiệu quả nhất mà nhiều người bỏ qua: paste code mẫu có sẵn trong project để AI hiểu style.
Tôi cần tạo PaymentService tương tự InvoiceService bên dưới.
Giữ nguyên pattern error handling, logging, và dependency injection.
[paste InvoiceService.cs]
PaymentService cần các method:
- ProcessPayment(int invoiceId, PaymentMethod method)
- RefundPayment(int paymentId, string reason)
- GetPaymentHistory(int invoiceId)AI sẽ copy đúng style: cùng cách inject dependency, cùng cách throw exception, cùng logging format. Kết quả consistent với codebase hiện tại mà không cần giải thích dài dòng.
Chỉ rõ điều kiện biên và edge case
AI rất giỏi happy path nhưng hay quên edge case. Bạn phải nhắc:
Viết method ValidateInvoice với các business rules:
- InvoiceNumber phải unique trong cùng TenantId
- TotalAmount > 0 và không vượt quá 999,999,999
- CustomerId phải tồn tại và thuộc cùng TenantId
- Không được tạo invoice cho customer đã bị soft-delete
- Nếu trùng InvoiceNumber, trả về lỗi cụ thể (không phải generic 400)Không nhắc edge case → AI bỏ qua. Nhắc rõ → AI xử lý đầy đủ. Đơn giản vậy thôi.
Level 2: Structured prompt — Tổ chức prompt có cấu trúc
Khi task phức tạp, viết tất cả thành một đoạn text dài sẽ khiến AI bỏ sót thông tin. Tổ chức prompt thành các phần rõ ràng giúp AI xử lý tốt hơn.
Dùng XML tags hoặc Markdown headers
<context>
Dự án .NET 8 Web API, multi-tenant, dùng EF Core + PostgreSQL.
Repository: không dùng repository pattern riêng, truy cập
trực tiếp qua DbContext.
</context>
<task>
Tạo endpoint GET /api/invoices với phân trang và filter.
</task>
<requirements>
- Filter theo: Status, CustomerId, DateRange (CreatedFrom, CreatedTo)
- Phân trang: PageNumber (default 1), PageSize (default 20, max 100)
- Sort: theo CreatedAt DESC mặc định, cho phép sort theo
TotalAmount, InvoiceNumber
- Response: PagedResult<InvoiceDto> gồm Items, TotalCount,
PageNumber, PageSize, TotalPages
- Tự động filter theo TenantId từ JWT (không cần client gửi)
- Không trả về record có IsDeleted = true
</requirements>
<constraints>
- Tất cả filter chạy trên database (IQueryable),
không filter trên memory
- Dùng AsNoTracking vì là read-only query
- Không dùng AutoMapper, map thủ công sang DTO
</constraints>AI đọc prompt này như đọc specification — rõ ràng từng phần, không bỏ sót requirement nào.
Pattern: Role + Context + Task + Format
Đây là template mình dùng nhiều nhất cho task coding:
Bạn là senior .NET developer đang làm việc trong dự án
multi-tenant SaaS.
[Context]
Hệ thống quản lý hóa đơn, dùng ASP.NET Core 8, EF Core 8,
PostgreSQL. Clean Architecture: WebApi → Application → Domain →
Infrastructure.
[Task]
Refactor OrderService.CalculateTotal() để xử lý đúng 3 loại
discount: percentage, fixed amount, và buy-X-get-Y-free.
[Yêu cầu format]
- Viết theo Strategy pattern
- Mỗi discount type là một class riêng implement IDiscountStrategy
- Include unit test cho mỗi strategy
- Comment giải thích business logic phức tạpPhần "Role" không phải trang trí. Khi bạn nói "senior .NET developer", AI sẽ viết code production-grade hơn — dùng best practices, error handling đầy đủ, naming convention chuẩn. Nói "junior developer" thì code sẽ đơn giản hơn, nhiều comment hơn.
Level 3: Chain of Thought — Bắt AI nghĩ trước khi code
Đây là kỹ thuật tạo ra sự khác biệt lớn nhất với các task phức tạp.
Vấn đề: AI nhảy thẳng vào code
Khi bạn đưa task phức tạp, AI thường bắt đầu viết code ngay — giống như developer mở IDE mà chưa nghĩ. Kết quả: code chạy được cho happy path nhưng thiếu sót nhiều thứ.
Giải pháp: Yêu cầu AI lập kế hoạch trước
Tôi cần implement chức năng auto-generate invoice number
với format INV-{YYYY}{MM}-{sequence}. Sequence reset mỗi tháng.
Hệ thống có thể có nhiều request đồng thời.
Trước khi viết code, hãy:
1. Phân tích các vấn đề có thể gặp (concurrency, race condition...)
2. Đề xuất 2-3 approach và so sánh ưu/nhược điểm
3. Chọn approach tốt nhất và giải thích lý do
4. Sau đó mới viết code implementationKết quả hoàn toàn khác. Thay vì code ngay (và có thể quên xử lý concurrent request), AI sẽ phân tích trước: dùng database sequence, dùng advisory lock của PostgreSQL, hay dùng optimistic concurrency. Rồi mới viết code — và code đó đã handle edge case từ đầu.
Dùng cho debugging
Chain of thought đặc biệt mạnh khi debug:
Endpoint GET /api/invoices trả về đúng data khi test với 1 user,
nhưng khi 2 user khác tenant gọi đồng thời, đôi khi user A
thấy invoice của tenant B.
Đừng đưa ra solution ngay. Hãy:
1. Liệt kê tất cả nguyên nhân có thể gây ra vấn đề này
2. Với mỗi nguyên nhân, giải thích cách verify
3. Xếp hạng từ khả năng cao nhất đến thấp nhất
4. Đề xuất cách fix cho nguyên nhân có khả năng cao nhấtNếu không có instruction này, AI có thể nhảy thẳng vào fix sai nguyên nhân. Với chain of thought, nó sẽ nghĩ qua: Global Query Filter có bị bypass ở đâu không? DbContext có đang share giữa requests không? Có raw SQL query nào quên WHERE TenantId không? Middleware set TenantId có race condition không?
Level 4: Few-shot prompting — Dạy AI bằng ví dụ
Đôi khi giải thích convention bằng lời rất dài dòng. Cho AI xem ví dụ thì nhanh hơn.
Dạy format output
Viết commit message cho các thay đổi sau đây.
Dùng format giống ví dụ:
Ví dụ 1:
Changes: Thêm validation cho CreateInvoice
Message: feat(invoice): add FluentValidation rules for CreateInvoiceCommand
Ví dụ 2:
Changes: Fix lỗi N+1 query trong GetOrders
Message: perf(order): fix N+1 query by adding Include for Customer
Ví dụ 3:
Changes: Cập nhật README hướng dẫn setup Docker
Message: docs: update README with Docker setup instructions
Bây giờ viết commit message cho:
Changes: Thêm endpoint export invoice ra PDF,
dùng QuestPDF library, có header logo và footer paginationAI sẽ sinh: feat(invoice): add PDF export endpoint using QuestPDF with header logo and pagination — đúng format, đúng convention, không cần giải thích quy tắc Conventional Commits.
Dạy coding style
Tôi cần viết thêm các method cho OrderService.
Đây là style hiện tại trong project:
// Ví dụ method hiện có
public async Task<Result<OrderDto>> GetByIdAsync(int id)
{
var order = await _context.Orders
.AsNoTracking()
.Where(o => o.Id == id)
.Select(o => new OrderDto
{
Id = o.Id,
OrderNumber = o.OrderNumber,
CustomerName = o.Customer.Name,
TotalAmount = o.TotalAmount,
Status = o.Status.ToString()
})
.FirstOrDefaultAsync();
if (order is null)
return Result<OrderDto>.NotFound($"Order {id} not found");
return Result<OrderDto>.Success(order);
}
Viết thêm method:
- GetByCustomerAsync(int customerId) — trả về list orders
- GetPendingAsync() — trả về orders có status Pending,
sort theo CreatedAt DESCAI nhìn ví dụ sẽ tự học: dùng Result<T> wrapper, dùng AsNoTracking, project sang DTO trong query, dùng is null thay vì == null, format error message cụ thể. Tất cả mà không cần bạn liệt kê từng rule.
Level 5: Iterative prompting — Xây dựng dần, không ôm hết một lần
Sai lầm phổ biến: nhồi hết requirement vào một prompt khổng lồ. Kết quả: AI bỏ sót nhiều thứ vì quá tải thông tin.
Chia task thành các bước nhỏ
Thay vì:
Viết hoàn chỉnh module quản lý payment gồm: entity, migration,
DTO, validator, command, handler, controller, unit test,
integration testHãy chia nhỏ:
Bước 1: "Thiết kế entity Payment và PaymentTransaction
với các relationship cần thiết."
[Review output, chỉnh sửa nếu cần]
Bước 2: "Tạo EF Core configuration và migration cho
hai entity trên. Index trên PaymentDate và InvoiceId."
[Review output]
Bước 3: "Viết CreatePaymentCommand + Handler + Validator
theo pattern tương tự CreateInvoiceCommand mà tôi đã share."
[Review output]
Bước 4: "Viết unit test cho PaymentValidator và
ProcessPaymentHandler."Mỗi bước AI tập trung vào một phần, output chất lượng cao hơn. Và bạn có cơ hội review từng bước — phát hiện sai sớm thay vì nhận cả đống code rồi mới thấy entity design sai từ đầu.
Refine dần kết quả
Sau khi AI sinh code, thay vì viết lại prompt từ đầu, hãy refine:
Code trên tốt rồi, nhưng cần sửa:
1. ProcessPayment nên throw PaymentException thay vì
generic Exception
2. Thêm retry logic khi gọi payment gateway
(tối đa 3 lần, exponential backoff)
3. Log lại mỗi lần retry với warning levelCách này hiệu quả hơn viết lại từ đầu vì AI giữ được context từ lần trước — nó hiểu codebase đang làm việc, chỉ cần sửa phần bạn chỉ ra.
Kỹ thuật nâng cao cho tình huống cụ thể
Prompt cho code review
Review đoạn code sau với focus vào:
- Performance: có N+1 query, unnecessary memory allocation không?
- Security: có SQL injection, missing authorization check không?
- Maintainability: naming, separation of concerns, magic numbers
Với mỗi issue tìm thấy:
- Đánh severity: Critical / Warning / Suggestion
- Giải thích vì sao đó là vấn đề
- Đưa ra code fix cụ thể
[paste code]Prompt cho refactoring
Refactor method CalculateOrderTotal() bên dưới.
Method đang dài 120 dòng và vi phạm Single Responsibility.
Yêu cầu:
- Tách thành các method nhỏ, mỗi method < 20 dòng
- Extract discount logic thành separate class
- Giữ nguyên behavior — tôi sẽ chạy test hiện có để verify
- Không thay đổi method signature public
- Giải thích lý do mỗi thay đổi
[paste code]Dòng "giữ nguyên behavior" và "không thay đổi method signature" rất quan trọng — nó ngăn AI "cải tiến" quá đà và phá vỡ code đang chạy.
Prompt cho learning
Giải thích cách PostgreSQL advisory lock hoạt động.
Tôi là .NET developer, hiểu về database lock cơ bản
(row lock, table lock) nhưng chưa biết advisory lock.
Giải thích:
- Advisory lock khác gì row lock / table lock?
- Khi nào nên dùng advisory lock?
- Ví dụ cụ thể trong C# + Npgsql
- Gotchas hay gặp khi dùng
Không cần giải thích lock cơ bản — tôi đã biết rồi.
Dòng cuối "không cần giải thích lock cơ bản" tiết kiệm token và thời gian. AI biết trình độ bạn đến đâu nên không dạy lại từ đầu.
Những sai lầm cần tránh
Sai lầm 1: Prompt quá mơ hồ
# Tệ
"Improve this code"
# Tốt
"Improve performance of this LINQ query.
Specifically: reduce number of database round trips,
add AsNoTracking since this is read-only,
and project to DTO instead of loading full entity.""Improve" nghĩa là gì? Readability? Performance? Security? Nếu bạn không nói rõ, AI sẽ tự đoán — và đoán không đúng ý bạn.
Sai lầm 2: Tin mọi thứ AI nói
AI có thể tự tin đưa ra method signature không tồn tại, library version sai, hay pattern không phù hợp. Đặc biệt với library mới hoặc ít phổ biến, AI hay bịa API.
Quy tắc mình dùng: AI sinh code → mình đọc và hiểu logic → mình verify bằng docs hoặc chạy thử → mới dùng. Không bao giờ copy-paste mà không đọc.
Sai lầm 3: Prompt quá dài, quá chi tiết
Nghe mâu thuẫn với "cung cấp context" nhưng không phải. Có sự khác biệt giữa context cần thiết và thông tin thừa:
# Quá nhiều thông tin không liên quan
"Tôi đang làm dự án cho công ty ABC, team có 5 người,
dùng Scrum, sprint 2 tuần. Hôm nay là thứ 4, deadline thứ 6.
Manager tên Hùng, anh ấy muốn feature này xong sớm.
Viết function validate email..."
# Đủ context
"Viết extension method ValidateEmail cho string trong C#.
Dùng Regex, return bool, handle null/empty input.
Cần validate cả format lẫn domain (không chấp nhận
disposable email domain)."Thông tin về team, sprint, manager không giúp AI viết code tốt hơn. Chỉ cung cấp context kỹ thuật liên quan trực tiếp đến task.
Sai lầm 4: Không verify output
Prompt engineering giỏi đến đâu, AI vẫn có thể sai. Đặc biệt với các trường hợp: logic phụ thuộc vào thứ tự thực thi, race condition mà AI không thấy vì chỉ nhìn static code, hoặc business rule phức tạp mà AI hiểu sai context.
Luôn review output. Luôn chạy test. Luôn đọc code trước khi commit.
Prompt template cho công việc hàng ngày
Đây là bộ template mình dùng thường xuyên nhất. Bạn có thể lưu lại và customize theo project.
Viết feature mới
[Context] Tech stack, architecture, relevant existing code
[Task] Mô tả feature cần implement
[Requirements] Business rules, edge cases, validation
[Constraints] Pattern phải follow, anti-pattern cần tránh
[Format] Structure output: file nào, method nào, test nàoDebug
[Symptom] Mô tả chính xác lỗi: error message, khi nào xảy ra
[Environment] Dev/Staging/Production, OS, runtime version
[Đã thử] Những gì đã thử mà không fix được
[Code] Paste code liên quan
[Request] Phân tích nguyên nhân trước, sau đó đề xuất fixCode review
[Code] Paste code cần review
[Focus] Những khía cạnh cần chú ý (performance, security...)
[Context] Đây là phần nào của hệ thống, traffic dự kiến
[Format] Severity + explanation + fix cho mỗi issueKết luận
Prompt engineering cho developer không phải là viết những câu prompt dài và phức tạp. Nó là kỹ năng giao tiếp rõ ràng ý định kỹ thuật — điều mà thực ra cũng giống kỹ năng viết ticket, viết spec, hay giải thích requirement cho đồng nghiệp.
Ba điều tạo ra khác biệt lớn nhất: cung cấp context kỹ thuật đầy đủ (stack, convention, code mẫu), yêu cầu AI nghĩ trước khi code (chain of thought), và chia task lớn thành bước nhỏ (iterative prompting).
Kỹ năng này không cần học một lần rồi xong. Model mới ra liên tục, khả năng thay đổi, cách prompt hiệu quả cũng thay đổi theo. Nhưng nền tảng thì giữ nguyên: bạn càng rõ ràng trong cách diễn đạt, AI càng cho kết quả tốt. Và đó cũng là kỹ năng giúp bạn trở thành developer giỏi hơn — kể cả khi không dùng AI.
Leave a comment
Your email address will not be published. Required fields are marked *