01 Catalog là gì & cách dùng
Đây không phải một danh sách quy tắc "học thuộc rồi áp máy móc", mà là một hệ thống giá trị (value system) về code sạch — đúc kết từ kinh nghiệm thực chiến. Mỗi mục được gắn mã hiệu theo nhóm để tham chiếu nhanh.
C · Comments
Mùi liên quan đến bình luận.
E · Environment
Build & chạy test.
F · Functions
Mùi của hàm.
G · General
Nhóm lớn nhất, đa dạng.
N · Names
Đặt tên.
T · Tests
Mùi của test.
Dùng khi review: đọc code (của mình hay người khác), thấy chỗ "ngứa mắt" thì đối chiếu danh mục và ghi mã hiệu vào nhận xét — vd "chỗ này G25 (magic number)", "hàm này F3 (flag argument)". Mã hiệu khiến phản hồi ngắn gọn, nhất quán, không cãi nhau cảm tính.
02 Comments (C1–C5)
Triết lý chung về comment: comment tốt nhất là comment không cần viết vì code đã tự nói. Năm mùi sau là các kiểu comment cần dọn:
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| C1 | Inappropriate Information | Đừng để metadata (tác giả, lịch sử đổi, số ticket) trong comment — đó là việc của VCS / issue tracker. |
| C2 | Obsolete Comment | Comment cũ / sai / lạc đề: cập nhật hoặc xóa ngay, đừng để nó nói dối. |
| C3 | Redundant Comment | Đừng comment thứ code đã tự nói (i++; // tăng i, Javadoc rỗng). |
| C4 | Poorly Written Comment | Đã viết thì viết cho tốt: đúng ngữ pháp, súc tích, đáng đọc. |
| C5 | Commented-Out Code | Xóa ngay code bị comment lại — VCS nhớ giùm, để lại chỉ gây hoang mang. |
03 Environment (E) & Functions (F)
Environment — build & test phải "một thao tác"
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| E1 | Build Requires More Than One Step | Build phải là một thao tác đơn giản — một lệnh / một nút, không phải chuỗi bước thủ công. |
| E2 | Tests Require More Than One Step | Chạy mọi test cũng chỉ bằng một lệnh / một nút. |
Functions — mùi của hàm
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| F1 | Too Many Arguments | Càng ít đối số càng tốt (0, 1, 2); tránh từ 3 trở lên. |
| F2 | Output Arguments | Tránh đối số đầu ra; muốn đổi trạng thái thì đổi chính object sở hữu hàm. |
| F3 | Flag Arguments | Đối số boolean = hàm làm nhiều việc → tách thành các hàm riêng. |
| F4 | Dead Function | Hàm không ai gọi → xóa đi. |
04 General — phần 1 (G1–G18)
Nhóm General là lớn & đa dạng nhất. Phần đầu xoay quanh trùng lặp, mức trừu tượng, code chết & tính nhất quán:
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| G1 | Multiple Languages in One Source File | Hạn chế trộn nhiều ngôn ngữ trong một file. |
| G2 | Obvious Behavior Is Unimplemented | Hành vi mà ai cũng kỳ vọng (least astonishment) lại không có. |
| G3 | Incorrect Behavior at the Boundaries | Sai ở các trường hợp biên — đừng tin vào trực giác, hãy test biên. |
| G4 | Overridden Safeties | Tắt cảnh báo / bỏ qua test thất bại là chuốc lấy rủi ro. |
| G5 | Duplication | DRY — mọi trùng lặp là một cơ hội trừu tượng hóa. Quy tắc tối quan trọng nhất nhóm này. |
| G6 | Code at Wrong Level of Abstraction | Tách rạch ròi khái niệm cấp cao khỏi chi tiết cấp thấp. |
| G7 | Base Classes Depending on Their Derivatives | Lớp cha không được phụ thuộc vào lớp con của nó. |
| G8 | Too Much Information | Interface hẹp, lộ ít — module tốt là module ít "mặt tiền". |
| G9 | Dead Code | Code không bao giờ chạy (if không thể đúng, catch không bao giờ ném) → xóa. |
| G10 | Vertical Separation | Khai báo biến / hàm gần nơi dùng. |
| G11 | Inconsistency | Làm một kiểu thì làm nhất quán mọi nơi (principle of least surprise). |
| G12 | Clutter | Bỏ thứ thừa: hàm rỗng, biến không dùng, comment vô nghĩa. |
| G13 | Artificial Coupling | Đừng buộc dính những thứ không liên quan tới nhau. |
| G14 | Feature Envy | Method "thèm" dữ liệu / hàm của lớp khác hơn lớp của chính mình. |
| G15 | Selector Arguments | Đối số chọn-hành-vi (cái false lủng lẳng) → tách thành nhiều hàm nhỏ. |
| G16 | Obscured Intent | Code quá cô đọng / khó hiểu làm ý định bị che lấp. |
| G17 | Misplaced Responsibility | Đặt mỗi thứ vào đúng nơi người đọc kỳ vọng nó thuộc về. |
| G18 | Inappropriate Static | Ưu tiên non-static; chỉ static khi chắc chắn không bao giờ cần đa hình. |
RAG G5 (Duplication) là mùi đáng săn nhất. Trong pipeline RAG, đoạn "nhúng rồi chuẩn hóa vector" hay bị copy ở nhiều nơi — gom lại một chỗ:
def index(chunk):
v = model.encode(chunk)
v = v / norm(v) # chuẩn hóa — lặp ở đây
store.add(v, chunk)
def search(query):
v = model.encode(query)
v = v / norm(v) # …và lặp y hệt ở đây
return store.knn(v, k=5)
def embed(text): # một nguồn chân lý duy nhất
v = model.encode(text)
return v / norm(v)
def index(chunk): store.add(embed(chunk), chunk)
def search(query): return store.knn(embed(query), k=5)
Vì sao (G5 · DRY): logic "nhúng + chuẩn hóa" gom vào một hàm embed; đổi cách chuẩn hóa chỉ sửa một chỗ, và index/search không bao giờ lệch nhau.
05 General — phần 2 (G19–G36)
Phần sau của General thiên về diễn đạt ý định: biến giải thích, đa hình, đóng gói điều kiện, hằng có tên, luật Demeter…
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| G19 | Use Explanatory Variables | Chẻ tính toán phức tạp thành các biến trung gian có tên mang nghĩa. |
| G20 | Function Names Should Say What They Do | date.add(5) mơ hồ → addDaysTo / daysLater. |
| G21 | Understand the Algorithm | Hiểu thật sự thuật toán, đừng "vọc tới khi qua test". |
| G22 | Make Logical Dependencies Physical | Module phụ thuộc phải hỏi trực tiếp, không giả định (vd dùng getMaxPageSize() thay cho hằng giả định). |
| G23 | Prefer Polymorphism to If/Else or Switch/Case | Cân nhắc đa hình trước khi viết switch theo loại (tôn trọng OCP). |
| G24 | Follow Standard Conventions | Theo quy ước chung của nhóm / ngôn ngữ. |
| G25 | Replace Magic Numbers with Named Constants | 86400 → SECONDS_PER_DAY; 55 → LINES_PER_PAGE. |
| G26 | Be Precise | Chính xác: xử lý null, làm tròn, đồng thời… đừng đoán bừa. |
| G27 | Structure over Convention | Ưu tiên ép buộc bằng cấu trúc (enum, type) hơn là quy ước "nhớ mà làm". |
| G28 | Encapsulate Conditionals | Trích boolean phức thành hàm tên rõ: if shouldBeDeleted(timer). |
| G29 | Avoid Negative Conditionals | if buffer.shouldCompact() dễ hơn if not buffer.shouldNotCompact(). |
| G30 | Functions Should Do One Thing | Hàm chỉ làm một việc — chẻ nhỏ nếu thấy nó "và…". |
| G31 | Hidden Temporal Couplings | Phụ thuộc thứ tự gọi phải hiện rõ trong chữ ký hàm. |
| G32 | Don't Be Arbitrary | Cấu trúc code phải có lý do; đừng tùy tiện đặt đâu cũng được. |
| G33 | Encapsulate Boundary Conditions | Gói điều kiện biên (vd level + 1) vào một biến / hàm, đừng rải khắp nơi. |
| G34 | Functions Should Descend Only One Level of Abstraction | Mỗi câu trong hàm thấp hơn tên hàm đúng một mức. |
| G35 | Keep Configurable Data at High Levels | Hằng cấu hình đặt ở tầng cao, truyền xuống thay vì chôn ở tầng thấp. |
| G36 | Avoid Transitive Navigation | Luật Demeter — "viết code nhút nhát"; tránh a.getB().getC().doSomething(). |
RAG G25 (magic number) & G19 (explanatory variable) thường đi cùng nhau — số "thần kỳ" lẫn biểu thức rối trong một dòng lọc kết quả:
def keep(hits):
return [h for h in hits
if cosine(q_vec, h.vector) >= 0.78 # 0.78 là gì?
and len(h.text) > 40] # 40 là gì?
RELEVANCE_THRESHOLD = 0.78 # G25: hằng có tên
MIN_CHUNK_CHARS = 40
def keep(hits):
def is_relevant(h): # G19: biến/hàm giải thích
similarity = cosine(q_vec, h.vector)
long_enough = len(h.text) > MIN_CHUNK_CHARS
return similarity >= RELEVANCE_THRESHOLD and long_enough
return [h for h in hits if is_relevant(h)]
Vì sao (G25 + G19): 0.78 và 40 giờ có tên nói rõ ý nghĩa; điều kiện rối được chẻ thành similarity / long_enough để dòng return đọc như tiếng Anh.
RAG G23 (đa hình thay if/else) & G28 (đóng gói điều kiện): thay vì if theo loại reranker và một biểu thức boolean dài, dùng đa hình + một hàm tên rõ:
def rerank(kind, q, hits):
if kind == "bm25": return bm25_rerank(q, hits)
elif kind == "cross": return cross_encoder(q, hits) # G23: switch theo loại
# đóng gói điều kiện?
if len(hits) > 0 and hits[0].score < 0.3 and not cached(q):
hits = expand(q, hits)
return hits
class Reranker(Protocol): # G23: đa hình, mỗi loại một lớp
def rerank(self, q, hits): ...
def needs_expansion(q, hits): # G28: đóng gói điều kiện thành tên rõ
return hits and hits[0].score < LOW_SCORE and not cached(q)
def rerank(reranker, q, hits):
out = reranker.rerank(q, hits)
return expand(q, out) if needs_expansion(q, out) else out
Vì sao (G23 + G28): chọn-theo-loại chuyển sang đa hình (thêm reranker mới = thêm một lớp, không sửa code đang gọi); biểu thức boolean rối được gói trong needs_expansion — đọc một dòng là hiểu.
RAG G36 (Avoid Transitive Navigation — luật Demeter): đừng đi xuyên một chuỗi object để với tới thứ bạn cần.
def top_source(answer):
# đi xuyên 4 cấp: biết quá nhiều về cấu trúc bên trong
return answer.context.hits[0].document.metadata.source
def top_source(answer):
return answer.top_source() # hỏi object, đừng moi ruột nó
# trong lớp Answer:
def top_source(self):
return self.context.best_source() # mỗi cấp chỉ nói với hàng xóm
Vì sao (G36 · Demeter): chuỗi a.getB().getC()… buộc bạn biết cấu trúc nội bộ của nhiều lớp — đổi một lớp là vỡ khắp nơi. "Code nhút nhát" chỉ nói chuyện với hàng xóm trực tiếp.
06 Names (N1–N7)
Tên là công cụ giao tiếp mạnh nhất trong code. Bảy heuristic về đặt tên:
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| N1 | Choose Descriptive Names | Tên rõ ràng, phản ánh đúng ý định — chọn kỹ, đổi không tiếc tay. |
| N2 | Choose Names at Appropriate Level of Abstraction | Tên theo khái niệm, không lộ chi tiết hiện thực. |
| N3 | Use Standard Nomenclature Where Possible | Dùng thuật ngữ ngành / tên pattern (Decorator, Visitor) khi hợp. |
| N4 | Unambiguous Names | Tên chỉ có một cách hiểu duy nhất. |
| N5 | Use Long Names for Long Scopes | Độ dài tên tỉ lệ với phạm vi: scope rộng → tên dài & rõ. |
| N6 | Avoid Encodings | Không tiền tố kiểu m_, không Hungarian notation. |
| N7 | Names Should Describe Side-Effects | Tên phản ánh mọi thứ hàm làm — đừng giấu hành vi ngầm. |
07 Tests (T1–T9)
Một bộ test sạch & đầy đủ là tấm lưới an toàn cho mọi refactor. Chín heuristic về test:
| Mã | Tên (EN) | Ý chính |
|---|---|---|
| T1 | Insufficient Tests | Test mọi thứ có thể hỏng, không chỉ "đủ dùng cho qua". |
| T2 | Use a Coverage Tool! | Công cụ coverage lộ ra những vùng chưa được test. |
| T3 | Don't Skip Trivial Tests | Test nhỏ dễ viết, giá trị tài liệu cao — đừng bỏ qua. |
| T4 | An Ignored Test Is a Question about an Ambiguity | Test bị @Ignore là một câu hỏi đang treo về điều mơ hồ. |
| T5 | Test Boundary Conditions | Chú ý đặc biệt các điều kiện biên. |
| T6 | Exhaustively Test Near Bugs | Lỗi đi theo nhóm; thấy một lỗi thì test thật kỹ quanh đó. |
| T7 | Patterns of Failure Are Revealing | Mẫu hình thất bại của các test hé lộ nguyên nhân gốc. |
| T8 | Test Coverage Patterns Can Be Revealing | Nhìn chỗ nào được / chưa được chạy qua cũng để lộ vấn đề. |
| T9 | Tests Should Be Fast | Test chậm rồi sẽ bị bỏ chạy — giữ cho nhanh. |
08 Ghi nhớ nhanh
Catalog = checklist review. Thấy mùi nào ghi mã đó (C·E·F·G·N·T) — phản hồi ngắn gọn & nhất quán.
G5 (DRY) là mùi quan trọng nhất: mọi trùng lặp là một cơ hội trừu tượng hóa.
G25 + G19: magic number → hằng có tên; biểu thức rối → biến giải thích.
G23 + G28: đa hình thay switch theo loại; đóng gói điều kiện boolean phức thành hàm tên rõ.
G36 (Demeter): hỏi object, đừng moi ruột nó — tránh a.getB().getC()….
Comments: comment tốt nhất là cái không cần viết — xóa C1/C3/C5, sửa C2/C4.
Tests: đủ & nhanh — test mọi thứ có thể hỏng (T1), dùng coverage (T2), giữ nhanh (T9).