Search

Prompt Engineering Cho Developer: Từ Zero Đến Hero

Prompt Engineering Cho Developer: Từ Zero Đến Hero

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 invoice

Kế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 claim

So 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ạp

Phầ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 implementation

Kế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ất

Nế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 pagination

AI 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 DESC

AI 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 test

Hã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 level

Cá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ào

Debug

[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 fix

Code 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 issue

Kế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.

Tags:
Culi Dev

Culi Dev

Enjoy coding, enjoy life!

Leave a comment

Your email address will not be published. Required fields are marked *

Your experience on this site will be improved by allowing cookies Cookie Policy