Coding Convention là gì? Quy ước viết code cho người và AI

Một trang web do AI tạo có thể chạy tốt ngay sau buổi đầu tiên. Sang tuần thứ hai, bạn bắt đầu thấy ba cách gọi API, bốn kiểu bắt lỗi, tệp giao diện dài hàng trăm dòng và cùng một cấu trúc dữ liệu được khai báo lặp lại ở nhiều nơi. Mỗi lần yêu cầu thêm tính năng, AI lại đọc tệp gần nhất rồi viết tiếp theo một hướng hơi khác.
Từng đoạn code riêng lẻ chưa chắc đã sai. Vấn đề nằm ở chỗ toàn dự án không có một cách làm chung.
Coding convention — hiểu đơn giản là quy ước viết và tổ chức code — là tập hợp quy tắc giúp người và AI làm việc theo cùng một chuẩn: cách trình bày, đặt tên, chia tệp, khai báo kiểu dữ liệu, xử lý lỗi, ghi nhật ký, kiểm thử và xác nhận một thay đổi đã đủ an toàn để bàn giao.
Nếu quy ước đặt tên (naming convention) giúp cả dự án dùng chung một vốn từ vựng, thì quy ước viết code quyết định cách các phần được ghép thành một chương trình có trật tự. Với người thực hành vibe coding, đây là lớp chỉ dẫn giúp AI không tự sáng tác lại cấu trúc dự án sau mỗi cuộc trò chuyện.
Sau khi đi qua các nguyên tắc chung, bài viết sẽ minh họa cách áp dụng quy ước trên những công nghệ phổ biến khi chọn tech stack: nổi bật nhất là Python (xử lý dữ liệu, tự động hóa) và Node.js/Next.js (ứng dụng web), cùng phần đối chiếu nhanh với Rust, Go và React Native để thấy vì sao mỗi ngôn ngữ cần bộ quy ước bản địa riêng.
Coding convention (quy ước viết code) là gì?
Có thể hiểu coding convention là quy ước viết và sắp xếp mã nguồn trong dự án. Nó rộng hơn rất nhiều so với chuyện dùng dấu nháy đơn hay nháy kép.
Một bộ quy ước hoàn chỉnh thường trả lời bảy lớp câu hỏi:
| Lớp quy ước | Câu hỏi cần trả lời |
|---|---|
| Đặt tên | Biến, hàm, lớp, thành phần giao diện và tệp được gọi thế nào? |
| Trình bày | Thụt lề, xuống dòng, dấu nháy và câu lệnh nạp thư viện xếp ra sao? |
| Kiểu dữ liệu | Khi nào cần ghi rõ kiểu dữ liệu cho đầu vào và đầu ra? |
| Tổ chức code | Phần tính toán, phần lấy dữ liệu và phần hiển thị đặt ở tệp nào? |
| Xử lý lỗi | Lỗi nào được bắt lại, ghi nhật ký, trả về hoặc dừng chương trình? |
| Kiểm thử | Thay đổi nào cần bài kiểm thử tự động hoặc kiểm tra lại giao diện? |
| Bàn giao | Phải chạy những lệnh kiểm tra nào trước khi nói công việc đã xong? |
Trong tài liệu kỹ thuật, ba khái niệm sau thường đi gần nhau:
- Coding style (phong cách trình bày): chủ yếu nói về hình thức mặt chữ của code.
- Coding convention (quy ước viết code): bổ sung cách tổ chức tệp, phân chia trách nhiệm và lựa chọn nhất quán trong dự án.
- Coding standard (tiêu chuẩn bắt buộc): những quy tắc nếu vi phạm sẽ bị công cụ kiểm tra tự động chặn lại.
Trong dự án cá nhân, bạn không cần tranh luận ranh giới giữa ba thuật ngữ này. Cách thực dụng là viết quy ước bằng ngôn ngữ dễ hiểu, rồi giao những phần kiểm tra máy móc cho các công cụ tự động căn chỉnh code, soát lỗi quy tắc, kiểm tra kiểu dữ liệu và chạy kiểm thử.
Bảy lớp của bộ quy ước viết code từ hình thức trình bày đến quy trình bàn giao và kiểm chứng
Sáu nguyên tắc dùng chung cho hầu hết dự án
Mỗi ngôn ngữ có thói quen riêng, nhưng sáu nguyên tắc dưới đây giữ nguyên giá trị ở hầu hết dự án.
1. Ưu tiên quy ước sẵn có trước sở thích cá nhân
Nếu dự án Python đang dùng dấu nháy kép và công cụ Ruff, đừng yêu cầu AI đổi toàn bộ sang dấu nháy đơn chỉ vì sở thích nhất thời. Một thay đổi hình thức trên hàng trăm dòng sẽ làm rối lịch sử Git và che mất thay đổi công thức thực sự.
Khi dự án chưa có quy ước, hãy chọn chuẩn phổ biến của ngôn ngữ đó. Khi đã có, hãy ưu tiên tính nhất quán trừ khi cách cũ gây lỗi hoặc dự án chủ động lên kế hoạch chuyển đổi.
2. Mỗi tệp chỉ nên đảm nhận một việc rõ ràng
Một tệp utils.py chứa đủ thứ từ gọi mạng, đổi đơn vị dữ liệu, tính chỉ báo đến gửi email sẽ rất khó kiểm tra khi có lỗi. Nếu bạn không thể mô tả nhiệm vụ của tệp bằng một câu ngắn, có thể nó đang ôm đồm quá nhiều việc.
Nên chia theo nhiệm vụ:
data_access.py
normalization.py
indicators.py
reporting.pyTránh đặt tên chung chung hoặc chắp vá:
utils.py
helpers_new.py
common_final.py3. Tách công thức nghiệp vụ khỏi đầu vào và giao diện
Một công thức tính toán không nên phụ thuộc trực tiếp vào nút bấm trên trang web hoặc cách một API bên ngoài đặt tên trường. Tách riêng từng lớp giúp bạn kiểm tra công thức bằng bộ dữ liệu nhỏ và đổi giao diện mà không phải viết lại phần lõi:
nguồn đầu vào → kiểm tra và chuẩn hóa → công thức nghiệp vụ → hiển thị hoặc lưu kết quảĐây cũng là nơi schema dữ liệu và data contract (quy ước dữ liệu) nối với quy ước viết code: điểm tiếp giáp giữa các lớp cần có cấu trúc đầu vào và đầu ra rõ ràng.
4. Bắt lỗi có chủ đích
Đừng bọc try/except hoặc try/catch quanh cả chương trình rồi bỏ qua mọi lỗi. Chỉ bắt lỗi khi chương trình biết rõ phải làm gì tiếp theo: thử lại, đổi thông báo, giải phóng tài nguyên hoặc trả về một kết quả lỗi rõ nguyên nhân.
Nên làm:
try:
prices = load_prices(symbol)
except SourceRateLimitError as error:
logger.warning("Nguồn tạm giới hạn tần suất", extra={"symbol": symbol})
raise RetryLaterError(symbol) from errorTránh làm:
try:
prices = load_prices(symbol)
except Exception:
pass5. Ghi chú giải thích lý do, khai báo kiểu mô tả cấu trúc
Ghi chú trong code không nên kể lại những gì câu lệnh đã nói rõ như total += value. Nó nên giải thích vì sao một trường hợp bị loại, đơn vị nào đang dùng hoặc giả định nào đứng sau công thức.
Khai báo kiểu dữ liệu trong Python và TypeScript không thay thế tài liệu, nhưng giúp trình soạn thảo, AI và công cụ kiểm tra biết hình dạng dự kiến của dữ liệu trước khi chương trình chạy.
6. “Hoàn thành” phải đi kèm bằng chứng kiểm tra
Một câu “đã sửa xong” là chưa đủ. Quy ước cần định nghĩa rõ lệnh kiểm tra tương ứng với loại dự án. Nếu một lệnh chưa chạy được, AI phải nói rõ thay vì tự suy đoán rằng code đã hợp lệ.
Minh họa 1 — Quy ước cho Python (dữ liệu và tự động hóa)
Trong các dự án phân tích dữ liệu và tự động hóa, Python là lựa chọn phổ biến nhất. Do Python dùng khoảng trắng đầu dòng để xác định khối lệnh, cách trình bày không chỉ là chuyện thẩm mỹ. PEP 8 là điểm xuất phát chuẩn mực cho cách xuống dòng, nạp thư viện và đặt tên trong Python.1
Đặt tên, khai báo kiểu và viết hàm
- Hàm, biến và tệp mã dùng chữ thường nối gạch dưới (
snake_case). - Lớp đối tượng dùng
PascalCase; hằng số dùngUPPER_SNAKE_CASE. - Hàm dùng chung và công thức tài chính nên ghi rõ kiểu dữ liệu đầu vào, đầu ra.
- Không dùng danh sách rỗng như
items=[]làm giá trị mặc định ngay trên dòng khai báo hàm; hãy đểNonerồi khởi tạo bên trong. - Hàm nên trả về một kiểu kết quả nhất quán, thay vì lúc trả bảng dữ liệu, lúc trả
None, lúc trả chuỗi báo lỗi.
from collections.abc import Sequence
def calculate_returns(close_prices: Sequence[float]) -> list[float]:
if len(close_prices) < 2:
return []
return [
current / previous - 1
for previous, current in zip(close_prices, close_prices[1:])
]Khai báo kiểu chưa bảo đảm mọi giá trị đều hợp lệ. Hàm vẫn cần kiểm tra các điều kiện nghiệp vụ như giá không âm, chuỗi thời gian đúng thứ tự hoặc dữ liệu không bị trộn đơn vị.
Sắp xếp thư viện và giữ ranh giới tệp
Các câu lệnh nạp thư viện (import) ở đầu tệp nên tách thành ba nhóm: thư viện chuẩn của Python, thư viện cài thêm và các tệp nội bộ. Tránh from module import * vì người đọc và công cụ khó biết hàm đến từ đâu.
Phần gọi mạng, đọc tệp hoặc lấy biến môi trường nên đặt ở ranh giới tiếp nhận dữ liệu. Công thức tính toán chỉ nhận đầu vào đã kiểm tra, không tự đi tìm khóa kết nối hay đọc .env giữa chừng (xem thêm cách lưu API key an toàn bằng Keyring và .env).
Quy ước riêng cho bảng dữ liệu Pandas
Bảng dữ liệu (DataFrame) của Pandas cho phép thêm cột và đổi kiểu rất linh hoạt, nhưng sự linh hoạt này cũng làm lỗi số liệu dễ bị che giấu. Khi làm việc với dữ liệu thị trường, bộ quy ước nên gắn liền với bước chuẩn hóa dữ liệu chứng khoán:
- kiểm tra cột bắt buộc trước khi tính;
- gọi cột bằng tên, không dựa vào số thứ tự cột nếu cấu trúc chưa cam kết cố định;
- ghi rõ đơn vị tiền tệ và múi giờ;
- hạn chế sửa đè trực tiếp lên bảng gốc (
inplace=True) để luồng biến đổi dễ theo dõi; - đặt tên bảng theo từng bước như
normalized_prices, không dùngdf1,df2; - không âm thầm thay toàn bộ ô trống (
null) bằng0.
Công cụ kiểm tra cho Python
Một cấu hình gọn cho dự án Python thường gồm:
| Mục đích | Công cụ thường dùng | Lệnh minh họa nếu dự án đã cấu hình |
|---|---|---|
| Tự động căn chỉnh code | Ruff formatter hoặc Black | ruff format --check . |
| Soát lỗi quy tắc | Ruff | ruff check . |
| Kiểm tra kiểu dữ liệu | mypy hoặc Pyright | mypy src |
| Chạy kiểm thử | pytest | pytest |
Đừng cài cả bốn công cụ chỉ để dự án trông phức tạp. Hãy bắt đầu bằng căn chỉnh code, soát lỗi quy tắc và kiểm thử cho phần tính toán quan trọng; thêm kiểm tra kiểu khi cấu trúc đã ổn định.
Minh họa 2 — Quy ước cho Node.js và Next.js (ứng dụng web)
Khi đưa kết quả phân tích lên giao diện web, Node.js và Next.js là cặp công nghệ rất thường gặp. Quy ước ở đây kế thừa JavaScript/TypeScript nhưng bổ sung quy tắc riêng về đường dẫn trang, thành phần giao diện và ranh giới giữa máy chủ với trình duyệt.
Chọn TypeScript cho code mới do AI tạo
TypeScript buộc dữ liệu truyền vào giao diện, dữ liệu API và kết quả trả về phải được viết rõ ràng. Next.js hỗ trợ sẵn TypeScript để phát hiện sớm nhiều lỗi ngay khi viết code.2
Nên làm:
type PriceCardProps = {
symbol: string;
closePrice: number;
changePercent: number | null;
};
export function PriceCard({
symbol,
closePrice,
changePercent,
}: PriceCardProps) {
// ...
}Tránh làm:
export function PriceCard(props: any) {
// ...
}Không cần khai báo kiểu cho mọi biến nhỏ lẻ đã hiển nhiên. Hãy tập trung vào các điểm giao tiếp: dữ liệu truyền vào giao diện, dữ liệu gửi/nhận qua API, cấu hình và kết quả của hàm dùng chung.
Tôn trọng tên tệp quy định sẵn của Next.js
Next.js dùng chính tên tệp để quyết định vai trò: page.tsx, layout.tsx, loading.tsx, error.tsx, route.ts. Thư mục bên trong app/ tạo thành đường dẫn trang web.3 Đây là quy định của bộ khung Next.js, không phải nơi để AI tùy ý đổi tên:
src/
├── app/
│ ├── api/
│ ├── dashboard/
│ │ ├── page.tsx
│ │ └── loading.tsx
│ └── layout.tsx
├── components/
│ ├── ui/
│ └── charts/
├── features/
│ └── portfolio/
├── lib/
│ ├── env.ts
│ └── validation.ts
└── types/Sơ đồ trên chỉ là một gợi ý. Quy ước quan trọng hơn là mỗi thư mục có trách nhiệm rõ và cả dự án không dùng đồng thời ba cách sắp xếp khác nhau.
Giữ ranh giới giữa máy chủ và trình duyệt
Trong Next.js App Router, các thành phần giao diện mặc định chạy ở phía máy chủ. Chỉ thêm 'use client' khi thực sự cần lưu trạng thái bấm chọn, hiệu ứng hoặc thao tác trên trình duyệt. Nếu AI đặt 'use client' ngay ở tệp bố cục cấp cao chỉ để sửa nhanh một lỗi nhỏ, nhiều phần bên dưới sẽ bị đẩy sang trình duyệt một cách dư thừa.
Quy ước nên yêu cầu rõ:
- giữ xử lý phía máy chủ làm mặc định;
- gom phần tương tác cần chạy trên trình duyệt vào thành phần nhỏ nhất hợp lý;
- không đưa khóa bí mật hoặc biến môi trường nội bộ của máy chủ xuống trình duyệt;
- tách phần lấy dữ liệu khỏi phần vẽ giao diện khi công thức bắt đầu phức tạp;
- kiểm tra dữ liệu gửi lên ngay tại máy chủ, không tin trực tiếp dữ liệu từ trình duyệt.
Phân định rõ phần chạy trên máy chủ và phần tương tác trên trình duyệt trong Next.js
Giới hạn trách nhiệm và công cụ kiểm tra
- Thành phần giao diện dùng
PascalCasevà tập trung vào một cụm hiển thị rõ ràng. - Hàm chia sẻ trạng thái (hook) bắt đầu bằng
use, chẳng hạnusePortfolioFilters(). - Hàm xử lý phía máy chủ nên nêu rõ hành động như
createWatchlist()hoặcloadPortfolio(). - Điểm trả dữ liệu API (
route.ts) cần trả mã trạng thái và cấu trúc lỗi nhất quán. - Ở tầng Node.js, thống nhất một kiểu nạp thư viện (
importhoặcrequire), dùng một trình quản lý gói duy nhất và che dữ liệu nhạy cảm khi ghi nhật ký.
Với Next.js hiện hành, ESLint dùng để soát lỗi quy tắc và Prettier dùng để căn chỉnh code.4 Từ Next.js 16, lệnh next lint đã bỏ để gọi trực tiếp ESLint:
npx eslint .
npx tsc --noEmit
npm test
npm run buildChỉ đưa vào hướng dẫn cho AI những lệnh thực sự tồn tại trong package.json.
Mở rộng sang Rust, Go, React Native: cùng nguyên tắc, khác cách thể hiện
Đặt Python và Next.js cạnh nhau, bạn sẽ thấy cả hai đều giải quyết cùng những bài toán gốc — chỉ khác tên công cụ và ranh giới cần bảo vệ:
| Chủ đề | Python | Next.js / TypeScript |
|---|---|---|
| Căn chỉnh code | Ruff formatter hoặc Black | Prettier |
| Soát lỗi quy tắc | Ruff (ruff check) | ESLint + cấu hình Next.js |
| Kiểm tra kiểu | Khai báo kiểu + mypy/Pyright | TypeScript (tsc --noEmit) |
| Tổ chức tệp chính | Chia tệp theo mảng nghiệp vụ | Tên tệp quy định trong app/ và thư mục components/ |
| Ranh giới quan trọng | Giữa bước đọc/ghi dữ liệu và công thức | Giữa máy chủ và trình duyệt; giữa API và giao diện |
| Kiểm thử | pytest | Vitest/Jest, React Testing Library |
| Khóa bí mật | Keyring, biến môi trường; không in ra log | Biến máy chủ; không gắn tiền tố công khai cho khóa |
Khi mở rộng sang các hệ sinh thái khác như Rust, Go hoặc React Native, bài học quan trọng nhất vẫn vậy: hãy dùng công cụ và thói quen bản địa của ngôn ngữ đó, đừng yêu cầu AI “viết Rust hay Go giống kiểu Python”.
| Hệ sinh thái | Quy ước bản địa nổi bật | Bộ kiểm tra thường gặp |
|---|---|---|
| Rust | Hàm/tệp snake_case, kiểu PascalCase; trả và xử lý lỗi tường minh qua Result | cargo fmt --check, cargo clippy, cargo test5 |
| Go | Tên gói ngắn, chữ thường; viết hoa chữ đầu để gọi ngoài gói; xử lý lỗi tường minh | gofmt hoặc go fmt, go vet, go test6 |
| React Native | Dùng TypeScript, chia thành phần giống React; tách tệp riêng khi khác nền tảng | ESLint, Prettier, kiểm tra kiểu và test; mặc định dùng TypeScript7 |
Go là ví dụ rõ nhất về việc giao chuyện trình bày cho máy: tài liệu chính thức khuyên chạy gofmt thay vì tranh luận cách căn lề. Rust cũng dùng rustfmt và Clippy để biến quy ước thành bước kiểm tra tự động. Còn React Native gần với Next.js ở lớp TypeScript/React nhưng thêm quy ước tách tệp .ios.tsx và .android.tsx khi giao diện hai hệ điều hành thực sự khác biệt.
Mẫu chỉ dẫn AI cho dự án kết hợp nhiều ngôn ngữ
Nếu một dự án dùng Python cho xử lý dữ liệu và Next.js cho giao diện, tệp chỉ dẫn cho AI (AGENTS.md hoặc CLAUDE.md) cần nói rõ ranh giới giữa hai phần. Khối dưới đây là điểm khởi đầu; hãy thay lệnh bằng script thực tế đang có trong dự án:
## Quy ước viết code
### Nguyên tắc chung
- Đọc quy ước và cấu trúc thư mục hiện có trước khi tạo tệp mới.
- Chỉ sửa trong phạm vi yêu cầu; không tự đổi đầu ra API, cấu trúc bảng hoặc đường dẫn trang.
- Tên định danh trong code dùng tiếng Anh; nội dung hiển thị trên giao diện dùng tiếng Việt.
- Không đưa API key, mật khẩu hoặc dữ liệu nhạy cảm vào code, nhật ký hay lịch sử Git.
- Tách riêng bước tiếp nhận đầu vào, chuẩn hóa, công thức nghiệp vụ và hiển thị.
### Python
- Tuân theo PEP 8; dùng `snake_case`, `PascalCase` và `UPPER_SNAKE_CASE` đúng vai trò.
- Ghi rõ kiểu dữ liệu cho hàm dùng chung và công thức tài chính.
- Kiểm tra cột bắt buộc, đơn vị, múi giờ và dữ liệu thiếu trước khi tính toán.
- Không bắt `Exception` chung rồi bỏ qua lỗi.
- Chạy `ruff format --check .`, `ruff check .` và `pytest` theo cấu hình dự án.
### Next.js và TypeScript
- Dùng TypeScript cho code mới; không dùng `any` để né kiểm tra kiểu.
- Giữ đúng tên tệp quy định của App Router.
- Giữ xử lý trên máy chủ làm mặc định; chỉ thêm `'use client'` ở thành phần tương tác cần thiết.
- Kiểm tra dữ liệu gửi lên ở máy chủ; không để lộ khóa bí mật xuống trình duyệt.
- Chạy `npx eslint .`, `npx tsc --noEmit`, bài kiểm thử liên quan và `npm run build` khi phù hợp.
### Bàn giao
- Liệt kê tệp đã sửa, hành vi thay đổi và các lệnh kiểm tra đã chạy.
- Nếu chưa chạy được một bước, nói rõ bước nào và lý do.
- Không báo cáo hoàn thành chỉ vì nhìn code trông có vẻ hợp lý.Tệp chỉ dẫn chung nên ngắn gọn hơn tài liệu kỹ thuật. Những quy trình dài, chỉ dùng trong một loại nhiệm vụ nhất định (hoặc khi làm việc với thư viện trong tài liệu Vnstock), có thể tách thành Agent Skills để AI chỉ nạp vào khi cần.
Cách đưa quy ước vào dự án mà không làm mọi thứ dừng lại
Bạn không cần dọn sạch toàn bộ code cũ trước khi phát triển tiếp:
- Ghi lại trạng thái hiện tại: công cụ căn chỉnh, soát lỗi, kiểm thử, cấu trúc thư mục và những quy ước đang được dùng thật.
- Chọn bộ tối thiểu: một công cụ căn chỉnh code, một công cụ soát lỗi, một lệnh kiểm tra kiểu và các bài kiểm thử quan trọng.
- Tách thay đổi hình thức khỏi thay đổi công thức: tránh căn chỉnh lại cả dự án trong cùng một lần lưu Git đang sửa lỗi tính toán.
- Đưa lệnh vào cấu hình chung: khai báo sẵn trong
pyproject.tomlhoặcpackage.jsonđể gọi nhanh và nhất quán. - Viết chỉ dẫn cụ thể cho AI: ghi chính xác lệnh cần chạy và phạm vi được sửa, không dùng câu chung chung như “hãy viết code sạch”.
- Siết dần từng bước: code mới phải đạt chuẩn ngay; code cũ được dọn dần khi chạm tới.
Áp dụng quy ước viết code từng bước cho dự án có sẵn mà không làm gián đoạn tiến độ
Một bộ quy ước tốt không phải bộ quy ước dày nhiều trang nhất. Nó là bộ quy ước đủ rõ để hai trợ lý AI độc lập cùng sửa một dự án mà không tạo ra hai phong cách viết và hai kiểu cấu trúc trái ngược nhau.
Điều cần nhớ
Coding convention (quy ước viết code) biến “cách tôi thường viết” thành nguyên tắc chung mà người khác, trợ lý AI và công cụ đều có thể kiểm tra. Nó bao gồm đặt tên, trình bày, kiểu dữ liệu, chia tệp, ranh giới dữ liệu, xử lý lỗi, kiểm thử và tiêu chuẩn bàn giao.
Dù bạn dùng Python cho dữ liệu, Node.js/Next.js cho giao diện web hay mở rộng sang Rust, Go, React Native, nguyên tắc cốt lõi vẫn không đổi: tôn trọng chuẩn bản địa của từng ngôn ngữ, giữ ranh giới sạch giữa các lớp và kiểm chứng bằng công cụ trước khi bàn giao.
AI giúp tạo code nhanh hơn rất nhiều. Chính vì vậy, quy ước càng cần được viết sớm để dự án vẫn đọc hiểu được và phát triển tiếp mà không phải đập đi làm lại. Khi quy ước viết code đã vào nếp trong lộ trình bắt đầu vibe coding, bước tiếp theo là quản lý lịch sử thay đổi qua bài Git cho vibe coding: Quản lý thay đổi do AI tạo ra.
Nội dung phục vụ học tập, nghiên cứu và tham khảo. Tên công cụ và lệnh có thể thay đổi theo phiên bản; hãy kiểm tra tài liệu hiện hành và cấu hình thật của dự án trước khi đưa vào hướng dẫn cho AI.
