AllianceProject Handbook
← Knowledge

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.

Cập nhật 12/09/2026codechuẩndev

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ặnBạn phải làm gì
Format, import thừa, biến không dùngFormatter + linterBật auto-fix khi save, đừng tranh luận
Kiểu dữ liệu, null, exhaustive switchTypeScript / compilerKhông tắt check, không ép kiểu bừa
Secret, dependency có lỗ hổngScanner trong CIKhô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ùng unknown rồ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ắcXấuTốt
Tên nói ý nghĩa, không nói kiểuuserArr, dataObjactiveMembers, orderDraft
Boolean đọc được thành câuflag, statusisExpired, canEditTask
Hàm bắt đầu bằng động từorderTotal()calculateOrderTotal()
Đơn vị nằm trong têntimeout, pricetimeoutMs, priceInVnd
Không viết tắt tự chếusrMgr, tmpCalc2userManager, 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:

  1. Không nuốt lỗi. catch {} rỗng bị cấm. Ít nhất phải log kèm ngữ cảnh.
  2. Không catch chung chung ở tầng thấp. Bắt lỗi ở nơi thật sự xử lý được nó.
  3. 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.
  4. 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.
  5. 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.
  6. 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: debug cho lúc phát triển, info cho mốc nghiệp vụ, warn cho thứ bất thường nhưng tự hồi phục, error cho 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.log cò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.
  • TODO khô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ành if (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ạicó thể chậm.