Yêu cầu với lập trình viên
Chuẩn viết code
Quy ước code của Alliance App. Phần nào máy kiểm tra được thì máy chặn, phần nào máy không thấy thì review chặn. Cả hai đều bắt buộc.
Nội dung bài
Nguyên tắc gốc
Code được đọc nhiều gấp mười lần được viết. Người đọc chủ yếu là bạn của sáu tháng sau, lúc đang vội sửa một bug lúc nửa đêm. Mọi quy ước dưới đây phục vụ người đó.
Chia trách nhiệm rõ:
| Loại vấn đề | Ai chặn | Bạn phải làm gì |
|---|---|---|
| Format, import thừa, biến không dùng | Formatter + linter | Bật auto-fix khi save, đừng tranh luận |
| Kiểu dữ liệu, null, exhaustive switch | TypeScript / compiler | Không tắt check, không ép kiểu bừa |
| Secret, dependency có lỗ hổng | Scanner trong CI | Không bao giờ commit secret |
| Đặt tên, tách hàm, mô hình hoá | Con người, ở review | Đọc phần dưới |
Đây là shift left ở mức cơ bản nhất: thứ máy bắt được thì đừng để tới lượt người, và đừng để tới lượt người dùng.
Bắt buộc về công cụ
- Formatter chạy tự động khi save và chạy lại trong pre-commit hook. Không ai được đẩy code sai format. Không tranh luận về style trong PR — formatter là trọng tài duy nhất.
- Linter không còn warning mới. Warning tồn tại từ trước được phép, warning do bạn thêm vào thì không.
- Không dùng
any(hoặc kiểu tương đương ở ngôn ngữ khác). Khi thật sự không biết kiểu, dùngunknownrồi thu hẹp bằng type guard. - Không tắt check bằng comment (
eslint-disable,@ts-ignore,# noqa…) nếu không kèm ngay dòng dưới một lời giải thích và cách xử lý đúng:
// SDK khai báo sai kiểu, đã báo ở issue #482. Bỏ dòng này khi SDK lên >= 3.2.
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
Tắt check không giải thích là lý do đủ để reject một PR.
Đặt tên
| Quy tắc | Xấu | Tốt |
|---|---|---|
| Tên nói ý nghĩa, không nói kiểu | userArr, dataObj | activeMembers, orderDraft |
| Boolean đọc được thành câu | flag, status | isExpired, canEditTask |
| Hàm bắt đầu bằng động từ | orderTotal() | calculateOrderTotal() |
| Đơn vị nằm trong tên | timeout, price | timeoutMs, priceInVnd |
| Không viết tắt tự chế | usrMgr, tmpCalc2 | userManager, discountedTotal |
Quy tắc về đơn vị (timeoutMs, priceInVnd) nghe vặt vãnh nhưng đã chặn được rất nhiều bug
tiền bạc và timeout ở các dự án khác. Bắt buộc áp dụng ở module Bán hàng.
Tên biến vòng lặp một chữ (i, j) chỉ được dùng cho vòng lặp đếm thuần tuý.
Kích thước và cấu trúc
- Một hàm làm một việc. Nếu phải viết "và" khi mô tả hàm, tách nó ra.
- Hàm dài quá 50 dòng thì phải giải thích được vì sao không tách. Không phải luật cứng, nhưng là câu hỏi bắt buộc ở review.
- Lồng nhau quá 3 tầng thì tách hàm hoặc dùng early return.
- Một file quá 400 dòng thì xem lại ranh giới trách nhiệm.
- Tham số quá 4 cái thì gom thành một object có tên.
Early return luôn được ưu tiên hơn else lồng nhau:
// Tránh
if (user) {
if (user.isActive) {
if (hasPermission(user)) {
return doWork(user);
}
}
}
// Dùng
if (!user) return null;
if (!user.isActive) return null;
if (!hasPermission(user)) return null;
return doWork(user);
Xử lý lỗi — phần bắt buộc
Đây là nơi sinh ra phần lớn bug lọt ra production, nên luật ở đây chặt:
- Không nuốt lỗi.
catch {}rỗng bị cấm. Ít nhất phải log kèm ngữ cảnh. - Không catch chung chung ở tầng thấp. Bắt lỗi ở nơi thật sự xử lý được nó.
- Mọi lời gọi mạng phải có timeout. Không có timeout nghĩa là app có thể treo mãi.
- Mọi màn hình có tải dữ liệu phải xử lý đủ 4 trạng thái: đang tải, có dữ liệu, rỗng, lỗi. Thiếu một trạng thái là thiếu acceptance criteria.
- Thông báo lỗi cho người dùng phải nói được họ làm gì tiếp theo. "Có lỗi xảy ra" là thông báo vô dụng.
- Lỗi không mong đợi phải được gửi về hệ thống thu thập lỗi, kèm mã phiên bản và luồng đang thực hiện, không kèm dữ liệu cá nhân.
Log
Log là công cụ điều tra sự cố, không phải bãi rác.
- Dùng mức log đúng:
debugcho lúc phát triển,infocho mốc nghiệp vụ,warncho thứ bất thường nhưng tự hồi phục,errorcho thứ cần người xem. - Mỗi log phải có đủ ngữ cảnh để hiểu mà không cần đọc code: định danh nghiệp vụ, hành động, kết quả.
- Cấm log: mật khẩu, token, số thẻ, nội dung tin nhắn, thông tin cá nhân người dùng. Vi phạm điều này là sự cố bảo mật, không phải lỗi code thường.
- Không dùng
console.logcòn sót lại làm log production.
Dependency
Thêm một thư viện mới là một quyết định, không phải một dòng lệnh:
- Thư viện nhỏ hơn 100 dòng code — tự viết, đừng thêm dependency.
- Trước khi thêm, kiểm tra: còn được bảo trì không, giấy phép có dùng được không, kích thước ảnh hưởng bundle bao nhiêu, có bao nhiêu dependency con.
- Thư viện chạm tới tiền, mã hoá, hoặc dữ liệu người dùng phải được chủ kỹ thuật duyệt.
- Không nâng phiên bản lớn của dependency chung trong một PR làm tính năng. Tách PR riêng.
Những thứ không được để lại trong code
- Code đã comment lại "để phòng khi cần" — git nhớ giùm rồi, xoá đi.
TODOkhông có tên người và không có issue kèm theo.- Tham số cấu hình viết cứng trong code (URL, khoá, thời gian chờ) — đưa ra config.
- Số ma thuật không tên:
if (status === 3)phải thànhif (status === OrderStatus.Paid). - File, hàm, biến không còn ai dùng.
Comment viết thế nào
Comment giải thích vì sao, không giải thích cái gì. Cái gì thì code tự nói.
// Vô dụng: tăng biến đếm lên 1
counter += 1;
// Có giá trị: server trả về trùng khi client retry, nên bỏ qua bản ghi đã thấy.
// Bỏ chỗ này đi thì báo cáo doanh số đếm đôi.
if (seenIds.has(order.id)) continue;
Quy tắc: mỗi khi bạn định viết comment giải thích một đoạn code khó hiểu, thử đặt lại tên hoặc tách hàm trước. Nếu vẫn cần comment thì viết.
Riêng cho mobile
- Không chạy tác vụ nặng trên luồng giao diện.
- Mọi danh sách có thể dài đều phải phân trang và tái sử dụng cell.
- Giải phóng listener, timer, subscription khi màn hình bị huỷ — rò rỉ bộ nhớ trên mobile biểu hiện thành app chậm dần rồi bị hệ điều hành thu hồi.
- Ảnh phải có kích thước phù hợp, không tải ảnh gốc rồi thu nhỏ.
- Mọi thao tác mạng phải giả định có thể thất bại và có thể chậm.

