Tìm hiểu cách viết prompt hướng dẫn phong cách cho Claude Code để thực thi quy tắc đặt tên, phân tầng, xử lý lỗi và ghi log, và phát hiện vi phạm sớm bằng các kiểm tra đơn giản.

Vi phạm hướng dẫn phong cách hiếm khi xuất hiện như một sai lầm lớn duy nhất. Chúng bắt đầu bằng những lựa chọn “gần đủ” nhỏ trông vô hại trong một pull request, rồi tích tụ cho tới khi codebase trở nên không đồng nhất và khó đọc hơn.
Sự trôi dạt về phong cách thường trông như thế này: một file dùng userID, file khác dùng userId, file thứ ba dùng uid. Một handler trả về { error: "..." }, file khác ném lỗi, file khác ghi log rồi trả về null. Mỗi thay đổi đều nhỏ, nhưng cùng nhau chúng tạo ra một repo nơi các mẫu không còn đáng tin cậy.
Tốc độ phát triển nhanh và nhiều người đóng góp càng làm tình hình tệ hơn. Mọi người sao chép những gì họ thấy, đặc biệt khi áp lực thời gian. Nếu mã gần đây nhất trong repo dùng một cách làm tắt, cách làm đó sẽ trở thành mẫu cho thay đổi tiếp theo. Sau vài tuần, “phong cách mặc định” không còn là hướng dẫn đã ghi mà là thứ vừa xảy ra.
Vì vậy mục tiêu phải là quy ước nhất quán, chứ không phải sở thích cá nhân. Câu hỏi không phải là “Tôi có thích tên này không?” mà là “Điều này có khớp với quy tắc đã thống nhất để người tiếp theo có thể theo mà không phải suy nghĩ không?”
Bắt lỗi sớm nghĩa là ngăn những mẫu xấu trước khi chúng trở thành thứ bị copy-paste. Tập trung vào mã mới và mã được sửa, sửa xuất hiện đầu tiên của một sự không nhất quán mới, và chặn việc merge tạo ra drift mới. Khi bạn báo vấn đề, hãy thêm một ví dụ ngắn về cách làm chuẩn để người ta có thể bắt chước lần sau.
Một ví dụ thực tế: một dev thêm endpoint API mới và ghi log body của request “chỉ để gỡ lỗi”. Nếu việc đó được merge, endpoint tiếp theo sẽ sao chép, và sớm thôi dữ liệu nhạy cảm sẽ xuất hiện trong log. Bắt nó ngay trong PR đầu tiên thì rẻ. Bắt sau khi nó đã lan ra thì tốn kém và rủi ro.
Hướng dẫn phong cách chỉ có tác dụng trong review nếu nó đọc như một checklist, chứ không phải một tập sở thích. Viết lại mỗi hướng dẫn thành một quy tắc có thể kiểm tra trên diff.
Tổ chức các quy tắc thành bốn nhóm để khó bỏ sót: đặt tên, phân tầng, xử lý lỗi và ghi log. Với mỗi nhóm, viết hai thứ: điều gì phải đúng và điều gì bị cấm.
Quyết định mức độ nghiêm ngặt của mỗi quy tắc ngay từ đầu:
Đặt phạm vi để review không biến thành refactor vô tận. Một quy tắc đơn giản hoạt động tốt: “mã mới và mã đã sửa phải tuân; mã hiện có không bị đụng tới trừ khi nó chặn sửa lỗi.” Nếu bạn muốn dọn dẹp, phân bổ thời gian cho nó như một task riêng.
Cũng xác định đầu ra bạn muốn từ một review để dễ hành động: một verdict pass/fail, danh sách vi phạm kèm file và số dòng, sửa đề xuất viết dưới dạng chỉnh sửa cụ thể, và một ghi chú rủi ro ngắn khi điều đó có thể gây bug hoặc rò rỉ.
Ví dụ: nếu một PR ghi log token người dùng thô, review nên fail theo “logging: không bao giờ ghi secrets” và gợi ý ghi một request ID thay thế.
Prompt phong cách thất bại khi nó nghe như sở thích. Một prompt review tốt đọc như một hợp đồng: những điều không thể thương lượng, ngoại lệ được đặt tên rõ ràng, và đầu ra dự đoán được.
Bắt đầu bằng hai khối ngắn: điều gì phải đúng và điều gì có thể linh động. Sau đó thêm một quy tắc quyết định: “Nếu không rõ, đánh dấu Needs Clarification. Không đoán.”
Ép bằng chứng. Khi công cụ báo một vi phạm, yêu cầu nó trích dẫn chính xác identifier và đường dẫn file, không phải mô tả mơ hồ. Ràng buộc này loại bỏ nhiều lần trao đổi qua lại.
Giữ phạm vi chặt: chỉ comment những dòng đã thay đổi và các đường dẫn mã bị ảnh hưởng trực tiếp. Nếu bạn cho phép refactor không liên quan, việc bắt phong cách sẽ biến thành “viết lại toàn file”, và mọi người dần mất niềm tin vào phản hồi.
Đây là cấu trúc bạn có thể dùng lại:
Role: strict style guide reviewer.
Input: diff (or files changed) + style guide rules.
Non-negotiables: [list].
Allowed exceptions: [list].
Scope: ONLY comment on changed lines and directly impacted code paths. No unrelated refactors.
Evidence: Every finding MUST include (a) file path, (b) exact identifier(s), (c) short quote.
Output: structured compliance report with pass/fail per category + minimal fixes.
Yêu cầu báo cáo giữ cùng các phần mỗi lần, ngay cả khi có phần “No issues found”: Naming, Layering, Error handling, Logging.
Nếu báo “service layer leaking DB details”, nó phải trích dẫn điều gì đó như internal/orders/service/order_service.go và gọi hàm cụ thể (ví dụ db.QueryContext) để bạn có thể sửa mà không tranh cãi ý nghĩa.
Hướng dẫn phong cách gắn chặt khi quy trình lặp lại được. Mục tiêu là để model kiểm tra quy tắc, không tranh luận khẩu vị, và làm điều đó cùng một cách mọi lần.
Dùng workflow hai bước đơn giản:
Ví dụ: một PR thêm endpoint mới. Pass 1 báo handler gọi thẳng PostgreSQL (layering), dùng tên request struct lẫn lộn (naming), và ghi log email đầy đủ (logging). Pass 2 thực hiện sửa tối thiểu: di chuyển gọi DB vào service hoặc repository, đổi tên struct, và che email trong log. Không sửa gì khác.
Vấn đề đặt tên trông nhỏ, nhưng tạo chi phí thật: người đọc hiểu sai ý, tìm kiếm khó khăn, và tên “gần giống” nhân lên.
Nêu rõ quy tắc đặt tên mà reviewer phải thi hành trong toàn bộ thay đổi: tên file, type xuất khẩu, hàm, biến, hằng, và test. Rõ ràng về kiểu chữ (camelCase, PascalCase, snake_case) và chọn một quy tắc cho các từ viết tắt (ví dụ APIClient vs ApiClient). Sau đó áp dụng mọi nơi.
Cũng chuẩn hoá từ vựng dùng chung: loại lỗi, trường log, và khóa config. Nếu log dùng request_id, không cho phép reqId ở file này và requestId ở file kia.
Một hướng dẫn cho reviewer giữ thực tế:
Check every new or renamed identifier. Enforce casing + acronym rules.
Flag vague names (data, info, handler), near-duplicates (userId vs userID), and names that contradict behavior.
Prefer domain language: business terms over generic tech words.
Yêu cầu một báo cáo ngắn: ba tên gây nhầm lẫn nhất, bất kỳ gần-trùng nào và chọn tên giữ lại, cùng các tên log/config/error không khớp chuẩn.
Quy tắc phân tầng hiệu quả nhất khi diễn đạt bằng ngôn ngữ giản dị: handler xử lý HTTP, service giữ business rules, repository giao tiếp DB.
Khóa chiều phụ thuộc. Handler được phép gọi service. Service được phép gọi repository. Repository không nên gọi service hay handler. Handler không nên import code database, helper SQL, hay ORM models. Nếu dùng package chia sẻ (config, time, IDs), giữ chúng không chứa logic app.
Giao việc cắt ngang vào đúng nơi. Validate thường thuộc biên (kiểm tra hình dạng request) và trong service cho business rules. Authorization thường bắt đầu ở handler (identity, scopes), nhưng service phải cưỡng chế quyết định cuối cùng. Mapping ở mép của từng lớp: handler map HTTP -> domain input, repository map DB rows -> domain types.
Cho prompt này để review cụ thể:
Check layering: handler -> service -> repository only.
Report any leaks:
- DB types/queries in handlers or services
- HTTP request/response types inside services or repositories
- repository returning DB models instead of domain objects
- auth/validation mixed into repository
For each leak, propose the smallest fix: move function, add interface, or rename package.
Làm báo cáo rõ ràng: nêu file, lớp nó thuộc về, import hoặc gọi nào vi phạm quy tắc, và thay đổi tối thiểu ngăn mẫu lan rộng.
Hầu hết tranh luận về phong cách trở nên căng khi có sự cố chạy production. Một chính sách rõ ràng về lỗi giữ cho việc sửa lỗi bình tĩnh vì mọi người biết “tốt” là gì.
Viết ra triết lý và thi hành. Ví dụ: “Bọc lỗi để thêm ngữ cảnh; tạo lỗi mới chỉ khi đổi nghĩa hoặc ánh xạ sang thông báo người dùng. Trả lỗi thô chỉ ở biên hệ thống.” Một câu như vậy ngăn nhiều mẫu ngẫu nhiên lan truyền.
Tách thông điệp cho người dùng khỏi chi tiết nội bộ. Thông báo người dùng nên ngắn và an toàn. Lỗi nội bộ có thể bao gồm tên thao tác và các định danh chính, nhưng không chứa secrets.
Trong review, kiểm tra một vài lỗi thường gặp: lỗi bị nuốt (chỉ log mà không trả), trả về mơ hồ (nil value với nil error sau khi có lỗi), và thông báo người dùng rò rỉ stack trace, câu truy vấn, token hoặc PII. Nếu hỗ trợ retry hoặc timeout, yêu cầu chúng được chỉ rõ.
Ví dụ: cuộc gọi checkout timeout. Người dùng thấy “Payment service is taking too long.” Ở nội bộ, bọc timeout và thêm op=checkout.charge và order ID để có thể tìm kiếm và hành động.
Log chỉ hữu ích khi mọi người ghi chúng cùng một kiểu. Nếu mỗi dev chọn câu chữ, level và trường riêng, việc tìm kiếm sẽ trở thành đoán mò.
Quy định level log là không thể thương lượng: debug cho chi tiết dev, info cho cột mốc bình thường, warn cho tình huống bất ngờ nhưng đã xử lý, và error khi hành động hướng tới người dùng thất bại hoặc cần chú ý. Giữ “fatal” hoặc “panic” hiếm và gắn với chính sách crash rõ ràng.
Log có cấu trúc quan trọng hơn câu hoàn chỉnh. Yêu cầu tên khóa ổn định để dashboard và cảnh báo không bị phá. Quyết định một tập nhỏ (ví dụ event, component, action, status, duration_ms) và giữ cho nó nhất quán.
Xử lý dữ liệu nhạy cảm như một ngưỡng cấm. Nêu rõ những gì không bao giờ được ghi: passwords, auth tokens, thẻ tín dụng đầy đủ, secrets, và dữ liệu cá nhân thô. Gọi ra những thứ trông vô hại nhưng không phải, như link reset mật khẩu, session ID, và body request đầy đủ.
Correlation ID làm cho việc gỡ lỗi xuyên các lớp trở nên khả thi. Yêu cầu request_id trong mọi dòng log liên quan đến một request. Nếu bạn ghi user_id, xác định khi nào được phép và cách biểu diễn khi người dùng ẩn danh hoặc thiếu.
Một khối prompt dùng lại:
Review the changes for logging conventions:
- Check level usage (debug/info/warn/error). Flag any level that does not match impact.
- Verify structured fields: require stable keys and avoid free-form context in the message.
- Confirm correlation identifiers: request_id on all request-bound logs; user_id only when allowed.
- Flag any sensitive data risk (tokens, secrets, personal data, request/response bodies).
- Identify noisy logs (in loops, per-item logs, repeated success messages) and missing context.
Return: (1) violations with file/line, (2) suggested rewrite examples, (3) what to add or remove.
Trước khi merge, làm một “safety pass” nhanh: bất kỳ log mới nào thiếu request_id cho công việc theo request, bất kỳ key mới nào đổi tên so với trước (userId vs user_id), bất kỳ error log nào thiếu thông tin cái gì thất bại (operation, resource, status), bất kỳ log có volume cao firing cho mỗi request, và mọi khả năng secrets hoặc dữ liệu cá nhân xuất hiện trong các trường hoặc thông điệp.
Hãy coi drift phong cách như một lỗi build, không phải gợi ý. Thêm một cổng nghiêm ngặt chạy trước khi merge và trả pass hoặc fail rõ ràng. Nếu một quy tắc bắt buộc bị phá (đặt tên, ranh giới lớp, an toàn log, xử lý lỗi), nó fail và chỉ ra chính xác file và dòng.
Giữ cổng ngắn. Một mẹo thực tế là yêu cầu checklist YES/NO cho mỗi quy tắc, và từ chối phê duyệt nếu có mục NO.
Checklist theo kích thước PR bắt được hầu hết vấn đề:
Khi công cụ gợi ý sửa, yêu cầu một đoạn snippet nhỏ tuân thủ cho mỗi quy tắc nó đụng tới. Điều đó ngăn phản hồi mơ hồ kiểu “đổi tên cho rõ ràng.”
Cách nhanh nhất để hướng dẫn phong cách thất bại là để lại nhiều chỗ cho diễn giải. Nếu hai reviewer đọc cùng một quy tắc và đi đến kết luận khác nhau, công cụ sẽ thi hành khẩu vị, chứ không phải chuẩn.
Đặt tên là ví dụ thường gặp. “Dùng tên rõ ràng” không thể kiểm tra. Siết lại thành thứ bạn có thể xác minh: “hàm là động từ (ví dụ createInvoice), boolean bắt đầu bằng is/has/can, type xuất khẩu là PascalCase.”
Một bẫy nữa là yêu cầu mọi thứ cùng lúc. Khi một prompt cố bao phủ đặt tên, phân tầng, lỗi, logging, test và hiệu năng, phản hồi sẽ nông. Tách các review thành các pass tập trung khi cần sâu, hoặc giữ prompt gate cho các quy tắc bắt buộc.
Các vấn đề thường khiến enforcement chệch:
Nếu bạn coi prompt như test, bạn sẽ có enforcement dự đoán được. Nếu coi nó như lời khuyên, vi phạm sẽ lẻn vào và nhân lên.
Chạy một pass nhanh trên diff (không phải toàn repo) và xác nhận:
Giữ một template prompt nhỏ và dán nó vào mỗi thay đổi:
Review ONLY the changed code against our rules for naming, layering, errors, and logging.
List mandatory violations first (with file + line if available). Then list optional suggestions.
End with either: “no mandatory violations found” or “mandatory violations found”.
Ví dụ: một hàm mới procUsr() trong handler viết trực tiếp vào PostgreSQL nên fail naming và layering dù feature hoạt động. Bắt lỗi ở đây ngăn copy-paste lan rộng sai lầm.
Một đồng đội thêm endpoint mới: POST /v1/invoices/{id}/send. Nó chạm handler, service và storage.
Ở pass đầu, bạn muốn một báo cáo, không phải ghi lại:
Pass 1 (report only)
You are a strict style checker. Read the patch.
Rules: naming must match our guide, handlers call services only, services call storage only, no SQL in handlers,
errors must be wrapped with context, logs must be structured and not leak PII.
Output: a numbered list of violations with file:line, rule name, and one-sentence impact. Do not propose fixes.
If a rule might be intentionally broken, ask one clarification question.
Những phát hiện điển hình: SendInvoiceNow() vs SendInvoice không khớp quy tắc đặt tên, handler gọi db.QueryRow trực tiếp, trả err thô không có ngữ cảnh, và log ồn ào như log.Printf("sending invoice %v", invoice) dump toàn object.
Pass hai yêu cầu sửa nhỏ nhất an toàn:
Pass 2 (minimal fix suggestions)
Using the violations list, propose the smallest code edits to comply.
Constraints: keep behavior the same, no refactors beyond what is needed, show suggested function names and where code should move.
For each fix, include 1-2 lines of example code.
Nếu phá quy tắc được phép, nói rõ ngay từ đầu: “Chỉ cho phép ngoại lệ nếu bạn thêm comment ngắn giải thích vì sao, và thêm task follow-up để bỏ ngoại lệ.”
Sau sửa, handler trở thành adapter mỏng, service nắm workflow, storage nắm query, lỗi trở thành fmt.Errorf("send invoice: %w", err), và log là một dòng sạch với trường an toàn (invoice ID, không phải toàn invoice).
Chọn một prompt đã được đội duyệt và coi nó như công cụ chung. Bắt đầu với thứ gây đau nhất trong review (drift tên, rò rỉ lớp, lỗi không đồng nhất, log không an toàn). Cập nhật prompt chỉ khi bạn thấy một vi phạm thực sự trong mã thực tế.
Giữ một khối quy tắc nhỏ trên cùng của prompt và dán nó vào mỗi review mà không đổi. Nếu mọi người sửa quy tắc mỗi lần, bạn sẽ không có chuẩn; bạn sẽ có tranh luận.
Một nhịp đơn giản hữu ích: một người thu thập các lỗi phong cách nổi bật trong tuần, và bạn thêm đúng một quy tắc rõ hơn hoặc một ví dụ tốt hơn.
Nếu bạn làm việc trong luồng build chat-driven như Koder.ai (koder.ai), đáng để chạy cùng các kiểm tra gate trong khi thay đổi, không chỉ ở cuối. Những tính năng như planning, snapshot và rollback có thể giúp giữ các sửa phong cách nhỏ và có thể hoàn tác trước khi bạn xuất source code.