ai-developer-skill-os 8.1.7 → 8.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +3 -3
- package/.agents/skills/qk-code-review/SKILL.md +189 -0
- package/.agents/skills/qk-code-review/references/ai/ai-anti-patterns.md +28 -0
- package/.agents/skills/qk-code-review/references/ai/v8-schema-validation.md +65 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/architecture-review-guide.md +212 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/async-concurrency-patterns.md +515 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/code-quality-universal.md +358 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/code-review-best-practices.md +136 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/common-bugs-checklist.md +124 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/error-handling-principles.md +492 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/n-plus-one-queries.md +309 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/performance-review-guide.md +387 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/security-review-guide.md +318 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/sql-injection-prevention.md +308 -0
- package/.agents/skills/qk-code-review/references/cross-cutting/xss-prevention.md +264 -0
- package/.agents/skills/qk-code-review/references/languages/angular.md +768 -0
- package/.agents/skills/qk-code-review/references/languages/c.md +890 -0
- package/.agents/skills/qk-code-review/references/languages/cpp.md +893 -0
- package/.agents/skills/qk-code-review/references/languages/csharp.md +519 -0
- package/.agents/skills/qk-code-review/references/languages/css-less-sass.md +661 -0
- package/.agents/skills/qk-code-review/references/languages/django.md +985 -0
- package/.agents/skills/qk-code-review/references/languages/fastapi.md +580 -0
- package/.agents/skills/qk-code-review/references/languages/go.md +993 -0
- package/.agents/skills/qk-code-review/references/languages/java.md +409 -0
- package/.agents/skills/qk-code-review/references/languages/java8.md +586 -0
- package/.agents/skills/qk-code-review/references/languages/kotlin.md +1018 -0
- package/.agents/skills/qk-code-review/references/languages/nestjs.md +593 -0
- package/.agents/skills/qk-code-review/references/languages/php.md +684 -0
- package/.agents/skills/qk-code-review/references/languages/python.md +1073 -0
- package/.agents/skills/qk-code-review/references/languages/qt.md +757 -0
- package/.agents/skills/qk-code-review/references/languages/react.md +871 -0
- package/.agents/skills/qk-code-review/references/languages/ruby.md +964 -0
- package/.agents/skills/qk-code-review/references/languages/rust.md +846 -0
- package/.agents/skills/qk-code-review/references/languages/svelte.md +1064 -0
- package/.agents/skills/qk-code-review/references/languages/swift.md +936 -0
- package/.agents/skills/qk-code-review/references/languages/typescript.md +1016 -0
- package/.agents/skills/qk-code-review/references/languages/vue.md +924 -0
- package/.agents/skills/qk-code-review/references/languages/zig.md +440 -0
- package/README.md +3 -3
- package/bin/install.js +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# Các Anti-Pattern Chất lượng Code Phổ quát (Universal Code Quality)
|
|
2
|
+
|
|
3
|
+
> Hướng dẫn nhận diện các anti-pattern về chất lượng code độc lập với ngôn ngữ, bao quát các chủ đề: Tái sử dụng code, Rò rỉ trừu tượng (Leaky abstraction), Phình to tham số (Parameter bloat), Câu lệnh điều kiện lồng nhau, Magic strings, TOCTOU, Cập nhật vô nghĩa (No-op update), v.v. Áp dụng cho mọi PR.
|
|
4
|
+
|
|
5
|
+
## Mục lục
|
|
6
|
+
|
|
7
|
+
- [Kiểm tra Tái sử dụng Code (Code Reuse)](#kiểm-tra-tái-sử-dụng-code)
|
|
8
|
+
- [Phình to Tham số (Parameter Bloat)](#phình-to-tham-số)
|
|
9
|
+
- [Rò rỉ Trừu tượng (Leaky Abstraction)](#rò-rỉ-trừu-tượng)
|
|
10
|
+
- [Lạm dụng Chuỗi (String Typing)](#lạm-dụng-chuỗi)
|
|
11
|
+
- [Điều kiện Lồng nhau (Nested Conditions)](#điều-kiện-lồng-nhau)
|
|
12
|
+
- [Biến thể Copy-Paste (Copy-Paste Variations)](#biến-thể-copy-paste)
|
|
13
|
+
- [Cập nhật Vô nghĩa (No-Op Updates)](#cập-nhật-vô-nghĩa)
|
|
14
|
+
- [Lỗi tương tranh TOCTOU (Race Conditions)](#lỗi-tương-tranh-toctou)
|
|
15
|
+
- [Thao tác quá rộng (Broad Operations)](#thao-tác-quá-rộng)
|
|
16
|
+
- [Trạng thái Dư thừa (Redundant State)](#trạng-thái-dư-thừa)
|
|
17
|
+
- [Checklist Chất lượng Chung](#checklist-chất-lượng-chung)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Kiểm tra Tái sử dụng Code (Code Reuse)
|
|
22
|
+
|
|
23
|
+
Trước khi chấp nhận code mới, hãy tìm kiếm trong source code xem đã có các hàm tiện ích (utilities) dùng chung nào chưa.
|
|
24
|
+
|
|
25
|
+
### Tận dụng Utility có sẵn
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# ❌ Viết lại logic nối đường dẫn —— Trong project đã có sẵn PathBuilder
|
|
29
|
+
def get_config_path(name):
|
|
30
|
+
base = os.environ.get("APP_ROOT", ".")
|
|
31
|
+
return os.path.join(base, "config", name + ".json")
|
|
32
|
+
|
|
33
|
+
# ✅ Dùng PathBuilder đã có
|
|
34
|
+
def get_config_path(name):
|
|
35
|
+
return PathBuilder.config(f"{name}.json")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// ❌ Tự viết tay hàm debounce —— Project đã có lodash hoặc utils/debounce.ts
|
|
40
|
+
function debounce(fn, ms) {
|
|
41
|
+
let timer;
|
|
42
|
+
return (...args) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ✅ Import hàm utility có sẵn
|
|
49
|
+
import { debounce } from "@/utils/debounce";
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Checklist:**
|
|
53
|
+
- [ ] Hàm mới viết có bị trùng tên hoặc trùng chức năng với utility có sẵn không?
|
|
54
|
+
- [ ] Đoạn logic viết inline có thể thay thế bằng hàm dùng chung không?
|
|
55
|
+
- [ ] Đã ngó qua thư mục `shared/utils` chưa?
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Phình to Tham số (Parameter Bloat)
|
|
60
|
+
|
|
61
|
+
### Hàm có quá nhiều tham số
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# ❌ Cứ có yêu cầu mới là nhét thêm 1 tham số
|
|
65
|
+
def create_user(name, email, role, team, active, avatar_url, timezone):
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
# ✅ Nhóm thành DataClass / Config Object
|
|
69
|
+
@dataclass
|
|
70
|
+
class CreateUserParams:
|
|
71
|
+
name: str
|
|
72
|
+
email: str
|
|
73
|
+
role: Role = Role.MEMBER
|
|
74
|
+
team: str | None = None
|
|
75
|
+
active: bool = True
|
|
76
|
+
avatar_url: str | None = None
|
|
77
|
+
timezone: str = "UTC"
|
|
78
|
+
|
|
79
|
+
def create_user(params: CreateUserParams) -> User:
|
|
80
|
+
...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// ❌ Hàm nhận 6 tham số cứng (positional arguments)
|
|
85
|
+
function renderWidget(title: string, width: number, height: number, theme: string, collapsible: boolean, icon: string) { ... }
|
|
86
|
+
|
|
87
|
+
// ✅ Pattern Options Object
|
|
88
|
+
interface WidgetOptions {
|
|
89
|
+
title: string;
|
|
90
|
+
width?: number;
|
|
91
|
+
height?: number;
|
|
92
|
+
theme?: "light" | "dark";
|
|
93
|
+
collapsible?: boolean;
|
|
94
|
+
icon?: string;
|
|
95
|
+
}
|
|
96
|
+
function renderWidget(options: WidgetOptions) { ... }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Checklist:**
|
|
100
|
+
- [ ] Hàm có từ 4 tham số trở lên không? Hãy đổi sang Options Object.
|
|
101
|
+
- [ ] Tham số mới thêm vào chỉ là một cái Cờ (Boolean flag)? Cân nhắc dùng Enum hoặc Strategy Pattern.
|
|
102
|
+
- [ ] Có tham số nào triệt tiêu nhau không? (Vd: `enable_x` và `disable_y`).
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Rò rỉ Trừu tượng (Leaky Abstraction)
|
|
107
|
+
|
|
108
|
+
### Để lọt chi tiết Implementation ra ngoài
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# ❌ Trả về Object của ORM —— Ép caller phải biết về thư viện SQLAlchemy
|
|
112
|
+
def get_users():
|
|
113
|
+
return session.query(User).filter(User.active == True).all()
|
|
114
|
+
|
|
115
|
+
# ✅ Trả về Domain Object (DTO), giấu tịt lớp Database đi
|
|
116
|
+
def get_active_users() -> list[UserDTO]:
|
|
117
|
+
rows = user_repo.find_active()
|
|
118
|
+
return [UserDTO.from_row(r) for r in rows]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// ❌ Component nhận trực tiếp cấu trúc của API Response
|
|
123
|
+
<UserCard user={apiResponse.data.results[0]} />
|
|
124
|
+
|
|
125
|
+
// ✅ Component chỉ nhận Domain Type, dùng Adapter để chuyển đổi
|
|
126
|
+
interface UserSummary {
|
|
127
|
+
displayName: string;
|
|
128
|
+
avatarUrl: string;
|
|
129
|
+
}
|
|
130
|
+
<UserCard user={adaptUser(apiResponse)} />
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Checklist:**
|
|
134
|
+
- [ ] Kiểu trả về (Return type) của hàm có làm lộ thư viện bên dưới không (ORM, HTTP Client, JSON format)?
|
|
135
|
+
- [ ] Component UI có bị dính chặt vào cấu trúc của hệ thống bên ngoài không?
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Lạm dụng Chuỗi (String Typing / Magic Strings)
|
|
140
|
+
|
|
141
|
+
### Dùng String trần trụi thay vì Enum/Constant
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# ❌ Magic strings rải rác khắp nơi, gõ sai chính tả là ăn lỗi
|
|
145
|
+
if status == "active": ...
|
|
146
|
+
if role == "admin": ...
|
|
147
|
+
|
|
148
|
+
# ✅ Gom thành Enum
|
|
149
|
+
class Status(StrEnum):
|
|
150
|
+
ACTIVE = "active"
|
|
151
|
+
SUSPENDED = "suspended"
|
|
152
|
+
|
|
153
|
+
if user.status == Status.ACTIVE: ...
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// ❌ Tên event bằng string —— Code editor không gợi ý, gõ sai không báo lỗi
|
|
158
|
+
emitter.emit("userCreated", data);
|
|
159
|
+
emitter.on("usercreated", handler); // BUG do gõ sai chữ C thành c
|
|
160
|
+
|
|
161
|
+
// ✅ Dùng Object Constant / Enum
|
|
162
|
+
const Events = {
|
|
163
|
+
USER_CREATED: "userCreated",
|
|
164
|
+
} as const;
|
|
165
|
+
emitter.emit(Events.USER_CREATED, data);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Checklist:**
|
|
169
|
+
- [ ] Có string nào dùng để đại diện cho Trạng thái (Status) / Loại (Type) mà chưa gom vào Enum không?
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Điều kiện Lồng nhau (Nested Conditions)
|
|
174
|
+
|
|
175
|
+
### Khối lệnh IF/ELSE lồng nhau sâu hoắm
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
# ❌ Chuỗi Ternary (Toán tử 3 ngôi) dài dòng
|
|
179
|
+
label = (
|
|
180
|
+
"Admin" if role == "admin" else
|
|
181
|
+
"Manager" if role == "manager" else
|
|
182
|
+
"Viewer" if role == "viewer" else
|
|
183
|
+
"Unknown"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# ✅ Dùng Lookup Table (Dictionary / Map)
|
|
187
|
+
ROLE_LABELS = {
|
|
188
|
+
"admin": "Admin",
|
|
189
|
+
"manager": "Manager",
|
|
190
|
+
"viewer": "Viewer",
|
|
191
|
+
}
|
|
192
|
+
label = ROLE_LABELS.get(role, "Unknown")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# ❌ IF lồng 3 lớp (Arrow code / Hadouken code)
|
|
197
|
+
def process(order):
|
|
198
|
+
if order is not None:
|
|
199
|
+
if order.items:
|
|
200
|
+
for item in order.items:
|
|
201
|
+
if item.price > 0:
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
# ✅ Early Return (Bảo vệ từ sớm)
|
|
205
|
+
def process(order):
|
|
206
|
+
if not order or not order.items:
|
|
207
|
+
return
|
|
208
|
+
for item in order.items:
|
|
209
|
+
if item.price <= 0:
|
|
210
|
+
continue
|
|
211
|
+
...
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Checklist:**
|
|
215
|
+
- [ ] Toán tử 3 ngôi lồng nhau $\ge$ 2 lớp?
|
|
216
|
+
- [ ] IF/ELSE lồng nhau $\ge$ 3 lớp?
|
|
217
|
+
- [ ] Có thể thay thế bằng Lookup Table, Match/Switch, hoặc Early Return không?
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Biến thể Copy-Paste (Copy-Paste Variations)
|
|
222
|
+
|
|
223
|
+
### Các khối code giống hệt nhau, chỉ khác mỗi cái tên
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// ❌ Copy-paste handler, chỉ thay đổi cái URL
|
|
227
|
+
async function deletePost(id: string) {
|
|
228
|
+
await fetch(`/api/posts/${id}`, { method: "DELETE" });
|
|
229
|
+
router.push("/posts");
|
|
230
|
+
}
|
|
231
|
+
async function deleteComment(id: string) {
|
|
232
|
+
await fetch(`/api/comments/${id}`, { method: "DELETE" });
|
|
233
|
+
router.push("/comments");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ✅ Parameter hóa (Truyền biến vào)
|
|
237
|
+
async function deleteResource(resource: string, id: string) {
|
|
238
|
+
await fetch(`/api/${resource}/${id}`, { method: "DELETE" });
|
|
239
|
+
router.push(`/${resource}`);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Checklist:**
|
|
244
|
+
- [ ] Có $\ge$ 2 đoạn code y hệt nhau, chỉ khác mỗi biến/URL/chữ không? Đưa thành hàm chung có tham số đi.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Cập nhật Vô nghĩa (No-Op Updates)
|
|
249
|
+
|
|
250
|
+
### Chọc vào State/DB ngay cả khi dữ liệu không đổi
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// ❌ Cứ mỗi 5 giây là setStatus một lần mặc kệ data có đổi hay không (khiến UI re-render)
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
const interval = setInterval(() => {
|
|
256
|
+
fetch("/api/status").then(r => r.json()).then(setStatus);
|
|
257
|
+
}, 5000);
|
|
258
|
+
return () => clearInterval(interval);
|
|
259
|
+
}, []);
|
|
260
|
+
|
|
261
|
+
// ✅ Kiểm tra trước khi set
|
|
262
|
+
setStatus(prev => isEqual(prev, data) ? prev : data);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
# ❌ Vòng lặp liên tục update DB dù giá trị bằng y hệt cũ
|
|
267
|
+
for item in items:
|
|
268
|
+
item.status = compute_status(item)
|
|
269
|
+
session.commit()
|
|
270
|
+
|
|
271
|
+
# ✅ Chỉ update nếu có thay đổi (Dirty check)
|
|
272
|
+
for item in items:
|
|
273
|
+
new_status = compute_status(item)
|
|
274
|
+
if item.status != new_status:
|
|
275
|
+
item.status = new_status
|
|
276
|
+
session.commit()
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Lỗi tương tranh TOCTOU (Time-of-Check-to-Time-of-Use)
|
|
282
|
+
|
|
283
|
+
### "Kiểm tra trước - Làm sau" trong môi trường Đồng thời (Concurrency)
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# ❌ Kiểm tra file tồn tại rồi mới mở -> Nhỡ lúc mở file nó bị xóa rồi thì sao?
|
|
287
|
+
if os.path.exists(path):
|
|
288
|
+
with open(path) as f:
|
|
289
|
+
data = f.read()
|
|
290
|
+
|
|
291
|
+
# ✅ Cứ mở file đi, lỗi thì Bắt (Try/Catch)
|
|
292
|
+
try:
|
|
293
|
+
with open(path) as f:
|
|
294
|
+
data = f.read()
|
|
295
|
+
except FileNotFoundError:
|
|
296
|
+
data = None
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
# ❌ Lỗi kinh điển hệ thống ngân hàng: Kiểm tra số dư rồi mới trừ tiền (Không Atomic)
|
|
301
|
+
if account.balance >= amount:
|
|
302
|
+
account.balance -= amount
|
|
303
|
+
|
|
304
|
+
# ✅ Bỏ vào Giao dịch (Transaction) hoặc Khóa (Lock)
|
|
305
|
+
with account.lock:
|
|
306
|
+
if account.balance < amount:
|
|
307
|
+
raise InsufficientFundsError()
|
|
308
|
+
account.balance -= amount
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Thao tác quá rộng (Broad Operations)
|
|
314
|
+
|
|
315
|
+
### Lấy nguyên xe tải hàng trong khi chỉ cần 1 củ tỏi
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// ❌ Lấy nguyên bảng Orders về RAM chỉ để lọc ra 2 đơn hàng Pending
|
|
319
|
+
const allItems = await db.query("SELECT * FROM orders");
|
|
320
|
+
const pending = allItems.filter(o => o.status === "pending");
|
|
321
|
+
|
|
322
|
+
// ✅ Đẩy việc lọc (Filter) xuống cho Database làm
|
|
323
|
+
const pending = await db.query(
|
|
324
|
+
"SELECT * FROM orders WHERE status = ?", ["pending"]
|
|
325
|
+
);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
# ❌ Đọc nguyên cái file log bự chà bá chỉ để lấy Dòng số 1
|
|
330
|
+
content = Path("log.txt").read_text()
|
|
331
|
+
first_line = content.split("\n")[0]
|
|
332
|
+
|
|
333
|
+
# ✅ Đọc đúng 1 dòng (Stream)
|
|
334
|
+
with open("log.txt") as f:
|
|
335
|
+
first_line = f.readline()
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Trạng thái Dư thừa (Redundant State)
|
|
341
|
+
|
|
342
|
+
### Trạng thái có thể Tự suy luận (Derived)
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// ❌ Lưu trữ thừa mứa: Đã có firstName và lastName rồi còn lưu fullName làm gì?
|
|
346
|
+
interface User {
|
|
347
|
+
firstName: string;
|
|
348
|
+
lastName: string;
|
|
349
|
+
fullName: string; // Redundant! Nếu update firstName mà quên update fullName là Toang!
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ✅ Tính toán tự động dựa trên gốc
|
|
353
|
+
interface User {
|
|
354
|
+
firstName: string;
|
|
355
|
+
lastName: string;
|
|
356
|
+
}
|
|
357
|
+
const fullName = `${user.firstName} ${user.lastName}`;
|
|
358
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Best Practice Review Code (Code Review Best Practices)
|
|
2
|
+
|
|
3
|
+
Bí kíp toàn diện để tiến hành Code Review một cách hiệu quả và chuyên nghiệp.
|
|
4
|
+
|
|
5
|
+
## Triết lý Review (Review Philosophy)
|
|
6
|
+
|
|
7
|
+
### Mục tiêu của Code Review
|
|
8
|
+
|
|
9
|
+
**Mục tiêu Chính:**
|
|
10
|
+
- Bắt lỗi (bugs) và các trường hợp biên (edge cases) trước khi đẩy lên Production.
|
|
11
|
+
- Đảm bảo code dễ bảo trì (maintainability) và dễ đọc (readability).
|
|
12
|
+
- Chia sẻ kiến thức (knowledge sharing) giữa các thành viên trong team.
|
|
13
|
+
- Ép buộc tuân thủ chuẩn code (coding standards) một cách nhất quán.
|
|
14
|
+
- Cải thiện các quyết định về Thiết kế và Kiến trúc.
|
|
15
|
+
|
|
16
|
+
**Mục tiêu Phụ:**
|
|
17
|
+
- Hướng dẫn (Mentor) cho các developer trẻ.
|
|
18
|
+
- Xây dựng văn hóa và sự tin tưởng trong team.
|
|
19
|
+
- Document lại các quyết định thiết kế thông qua quá trình thảo luận.
|
|
20
|
+
|
|
21
|
+
### Code Review KHÔNG PHẢI là:
|
|
22
|
+
|
|
23
|
+
- Một công cụ gác cổng hạch sách để cản trở tiến độ.
|
|
24
|
+
- Một nơi để phô trương kiến thức cá nhân.
|
|
25
|
+
- Nơi để bắt bẻ từng dấu phẩy, dấu cách (Việc đó để linter tự động làm).
|
|
26
|
+
- Nơi để ép người khác viết lại code theo sở thích cá nhân của mình.
|
|
27
|
+
|
|
28
|
+
## Thời điểm Review (Review Timing)
|
|
29
|
+
|
|
30
|
+
### Khi nào thì Review?
|
|
31
|
+
|
|
32
|
+
| Kích hoạt (Trigger) | Hành động (Action) |
|
|
33
|
+
|---------|--------|
|
|
34
|
+
| PR vừa mở | Review trong vòng 24 giờ, lý tưởng nhất là trong ngày |
|
|
35
|
+
| PR đã sửa theo yêu cầu | Re-review lại trong vòng 4 giờ |
|
|
36
|
+
| Phát hiện lỗi chí mạng (Blocking) | Thông báo ngay lập tức |
|
|
37
|
+
|
|
38
|
+
### Phân bổ thời gian
|
|
39
|
+
|
|
40
|
+
- **PR Nhỏ (<100 lines)**: 10-15 phút
|
|
41
|
+
- **PR Vừa (100-400 lines)**: 20-40 phút
|
|
42
|
+
- **PR Lớn (>400 lines)**: Yêu cầu chia nhỏ ra, hoặc dành >60 phút để review
|
|
43
|
+
|
|
44
|
+
## Các cấp độ Review (Review Depth Levels)
|
|
45
|
+
|
|
46
|
+
### Level 1: Skim Review (Lướt nhanh - 5 phút)
|
|
47
|
+
- Đọc mô tả PR và các Issues được đính kèm.
|
|
48
|
+
- Kiểm tra trạng thái của CI/CD (Test có pass không).
|
|
49
|
+
- Lướt qua danh sách các file thay đổi.
|
|
50
|
+
- Xác định xem có cần Review sâu hơn không.
|
|
51
|
+
|
|
52
|
+
### Level 2: Standard Review (Chuẩn mực - 20-30 phút)
|
|
53
|
+
- Đọc lướt toàn bộ luồng code (Walkthrough).
|
|
54
|
+
- Kiểm tra logic có đúng không.
|
|
55
|
+
- Kiểm tra xem có viết Test Cover không.
|
|
56
|
+
- Quét nhanh các vấn đề bảo mật.
|
|
57
|
+
|
|
58
|
+
### Level 3: Deep Review (Chuyên sâu - >60 phút)
|
|
59
|
+
- Đánh giá kiến trúc hệ thống.
|
|
60
|
+
- Phân tích hiệu năng (Performance).
|
|
61
|
+
- Kiểm toán Bảo mật (Security audit).
|
|
62
|
+
- Khám phá các rủi ro ở trường hợp biên (Edge cases).
|
|
63
|
+
|
|
64
|
+
## Nguyên tắc Giao tiếp (Communication Guidelines)
|
|
65
|
+
|
|
66
|
+
### Thái độ và Ngôn từ
|
|
67
|
+
|
|
68
|
+
**Sử dụng ngôn từ hợp tác:**
|
|
69
|
+
- "Bạn nghĩ sao về việc..." thay vì "Bạn phải làm thế này..."
|
|
70
|
+
- "Chúng ta có thể thử cân nhắc..." thay vì "Cái này sai rồi!"
|
|
71
|
+
- "Mình hơi thắc mắc chỗ này..." thay vì "Tại sao bạn không..."
|
|
72
|
+
|
|
73
|
+
**Cụ thể và có tính Action (Actionable):**
|
|
74
|
+
- Đưa ra ví dụ code (code examples) khi đề xuất thay đổi.
|
|
75
|
+
- Kèm link đến tài liệu (docs) hoặc các cuộc thảo luận cũ.
|
|
76
|
+
- Giải thích "TẠI SAO" (Why) lại đưa ra lời khuyên đó.
|
|
77
|
+
|
|
78
|
+
### Xử lý Bất đồng (Handling Disagreements)
|
|
79
|
+
|
|
80
|
+
1. **Cố gắng thấu hiểu**: Đặt câu hỏi làm rõ ý của người kia.
|
|
81
|
+
2. **Ghi nhận điểm đúng**: Cho thấy bạn đã suy nghĩ ở góc độ của họ.
|
|
82
|
+
3. **Đưa ra bằng chứng**: Dùng benchmark, docs, hoặc ví dụ cụ thể.
|
|
83
|
+
4. **Escalate (Nâng cấp) nếu cần**: Mời Senior Dev hoặc Architect vào phân xử.
|
|
84
|
+
5. **Biết khi nào nên dừng lại**: Không phải cuộc tranh cãi nào cũng đáng để sống chết bảo vệ.
|
|
85
|
+
|
|
86
|
+
## Phân loại Mức độ Ưu tiên (Review Prioritization)
|
|
87
|
+
|
|
88
|
+
### 🔴 Must Fix (Bắt buộc sửa / Blocking)
|
|
89
|
+
- Lỗ hổng Bảo mật (Security vulnerabilities).
|
|
90
|
+
- Nguy cơ làm hỏng / mất dữ liệu (Data corruption).
|
|
91
|
+
- Các thay đổi làm gãy (Breaking changes) mà không có kịch bản chuyển đổi (migration).
|
|
92
|
+
- Lỗi hiệu năng chí mạng.
|
|
93
|
+
- Quên không bắt lỗi (Error handling) ở các tính năng hiển thị cho End-user.
|
|
94
|
+
|
|
95
|
+
### 🟡 Should Fix (Nên sửa / Important)
|
|
96
|
+
- Bỏ sót chưa viết Test (Test coverage gaps).
|
|
97
|
+
- Rủi ro hiệu năng ở mức vừa phải.
|
|
98
|
+
- Code bị lặp lại (Duplication).
|
|
99
|
+
- Đặt tên tối nghĩa hoặc Cấu trúc rối rắm.
|
|
100
|
+
- Logic quá phức tạp mà không có comment giải thích.
|
|
101
|
+
|
|
102
|
+
### 🟢 Nice to Have (Có thì tốt / Non-blocking)
|
|
103
|
+
- Sở thích về Style code (nằm ngoài scope của Linter).
|
|
104
|
+
- Các tối ưu nhỏ lẻ (Minor optimizations).
|
|
105
|
+
- Thêm thắt vài Test case phụ.
|
|
106
|
+
- Cải thiện câu từ trong Document.
|
|
107
|
+
|
|
108
|
+
## Anti-Patterns cần tránh
|
|
109
|
+
|
|
110
|
+
### Anti-Patterns của người Review
|
|
111
|
+
- **Rubber stamping (Đóng dấu bừa)**: Bấm Approve (Duyệt) mà không thèm xem code.
|
|
112
|
+
- **Bike shedding (Tranh cãi chuyện ruồi muỗi)**: Cãi nhau nảy lửa về những tiểu tiết vớ vẩn.
|
|
113
|
+
- **Scope creep (Tiện tay dắt dê)**: "Nhân tiện bạn đang sửa file này, bạn làm thêm cái tính năng X này nhé..."
|
|
114
|
+
- **Ghosting (Bốc hơi)**: Thả 1 đống Request Changes rồi lặn mất tăm, không thèm quay lại xem người ta sửa chưa.
|
|
115
|
+
- **Perfectionism (Chủ nghĩa hoàn hảo)**: Chặn PR (Block) chỉ vì vài khác biệt nhỏ về phong cách cá nhân.
|
|
116
|
+
|
|
117
|
+
### Anti-Patterns của người Viết Code (Author)
|
|
118
|
+
- **Mega PRs (PR Siêu to khổng lồ)**: Gửi 1 cục PR dài >1000 dòng bắt người ta ngồi đọc.
|
|
119
|
+
- **No context (Không có ngữ cảnh)**: Để trống mô tả PR, không link issue nào.
|
|
120
|
+
- **Defensive responses (Xù lông nhím)**: Cãi lại mọi lời góp ý, khăng khăng mình đúng.
|
|
121
|
+
- **Silent updates (Sửa trong im lặng)**: Lẳng lặng commit sửa code nhưng không thèm Reply lại các comment báo cho người Review biết.
|
|
122
|
+
|
|
123
|
+
## Số liệu và Cải tiến (Metrics and Improvement)
|
|
124
|
+
|
|
125
|
+
### Nên theo dõi các Số liệu sau
|
|
126
|
+
- Thời gian từ lúc mở PR đến lúc có Review đầu tiên (Time to first review).
|
|
127
|
+
- Tổng thời gian hoàn thành một vòng Review (Review cycle time).
|
|
128
|
+
- Số vòng Review phải đập đi xây lại (Number of review rounds).
|
|
129
|
+
- Tỷ lệ lỗi lọt qua lưới (Defect escape rate).
|
|
130
|
+
- Tỷ lệ Cover của quá trình Review.
|
|
131
|
+
|
|
132
|
+
### Cải tiến liên tục
|
|
133
|
+
- Tổ chức các buổi Retrospective để bàn về quy trình Review.
|
|
134
|
+
- Chia sẻ bài học từ các con Bug lọt ra Production.
|
|
135
|
+
- Cập nhật các Checklist Review liên tục dựa trên các lỗi hay gặp.
|
|
136
|
+
- Tuyên dương những bài Review chất lượng, bắt được lỗi hiểm hóc.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Checklist Lỗi Phổ biến (Common Bugs Checklist)
|
|
2
|
+
|
|
3
|
+
Bảng tra cứu nhanh các mẫu lỗi thường gặp, được phân loại theo từng nhóm. Để xem ví dụ chi tiết, giải thích sâu hơn và checklist đầy đủ, vui lòng tham khảo cẩm nang của từng ngôn ngữ cụ thể được liên kết bên dưới.
|
|
4
|
+
|
|
5
|
+
## Các vấn đề Phổ quát (Universal Issues)
|
|
6
|
+
|
|
7
|
+
### Lỗi Logic
|
|
8
|
+
- [ ] Lỗi lệch một đơn vị (Off-by-one errors) trong vòng lặp và truy xuất mảng.
|
|
9
|
+
- [ ] Lỗi logic Boolean (Vi phạm định luật De Morgan).
|
|
10
|
+
- [ ] Quên kiểm tra null/undefined.
|
|
11
|
+
- [ ] Lỗi tương tranh (Race conditions) trong môi trường đồng thời (concurrent).
|
|
12
|
+
- [ ] Dùng sai toán tử so sánh (Ví dụ: `==` thay vì `===`, hoặc `=` thay vì `==`).
|
|
13
|
+
- [ ] Tràn số nguyên (Integer overflow/underflow).
|
|
14
|
+
- [ ] Sai số khi so sánh số thực dấu phẩy động (Floating point comparison issues).
|
|
15
|
+
|
|
16
|
+
### Quản lý Tài nguyên
|
|
17
|
+
- [ ] Rò rỉ bộ nhớ (Memory leaks) do quên đóng connection, listener.
|
|
18
|
+
- [ ] Quên đóng File handles.
|
|
19
|
+
- [ ] Quên trả lại Database connections vào Pool.
|
|
20
|
+
- [ ] Quên gỡ Event listeners khi không còn dùng.
|
|
21
|
+
- [ ] Quên xóa Timers/intervals (gọi `clearTimeout` / `clearInterval`).
|
|
22
|
+
|
|
23
|
+
### Xử lý Lỗi (Error Handling)
|
|
24
|
+
- [ ] Nuốt lỗi (Swallowed exceptions) bằng các khối `catch` để trống.
|
|
25
|
+
- [ ] Catch lỗi quá rộng (Generic exception handling) làm che mất lỗi thực sự.
|
|
26
|
+
- [ ] Quên đẩy (propagate) lỗi lên cấp trên.
|
|
27
|
+
- [ ] Ném ra (Throw) sai loại Exception.
|
|
28
|
+
- [ ] Thiếu khối `finally` để dọn dẹp tài nguyên.
|
|
29
|
+
|
|
30
|
+
## TypeScript/JavaScript
|
|
31
|
+
|
|
32
|
+
- [ ] Dùng `==` thay vì `===`.
|
|
33
|
+
- [ ] Lạm dụng `any` — Khuyến khích dùng type cụ thể hoặc `unknown` kết hợp type guards.
|
|
34
|
+
- [ ] Quên `await` khi gọi hàm bất đồng bộ.
|
|
35
|
+
- [ ] Không bắt lỗi promise rejections (Thiếu `try-catch` khi gọi `await`).
|
|
36
|
+
- [ ] Bị mất context `this` khi gọi callback.
|
|
37
|
+
- [ ] Quên truyền prop `key` khi render danh sách (list).
|
|
38
|
+
- [ ] Closure vô tình giữ lại biến cũ của vòng lặp (stale loop variable).
|
|
39
|
+
- [ ] Gọi `parseInt` mà không truyền tham số cơ số (radix).
|
|
40
|
+
- [ ] Sửa đổi trực tiếp (Modify) mảng/object trong khi đang lặp qua nó.
|
|
41
|
+
|
|
42
|
+
## React / React 19
|
|
43
|
+
|
|
44
|
+
- [ ] Gọi Hooks bên trong lệnh if hoặc vòng lặp (Vi phạm Rules of Hooks).
|
|
45
|
+
- [ ] Mảng phụ thuộc (dependency array) của `useEffect` bị thiếu hoặc sai.
|
|
46
|
+
- [ ] `useEffect` thiếu hàm cleanup (đối với các subscriptions, timers, fetches).
|
|
47
|
+
- [ ] Lạm dụng `useEffect` để tính toán state suy diễn (Derived state) — thay vào đó hãy dùng `useMemo`.
|
|
48
|
+
- [ ] Lạm dụng `useMemo`/`useCallback`, hoặc dùng mà không đi kèm `React.memo`.
|
|
49
|
+
- [ ] Định nghĩa Component con ngay bên trong Component cha (Khiến component bị re-mount mỗi lần render).
|
|
50
|
+
- [ ] Truyền props không ổn định (inline object/function) cho các component đã được `memo`.
|
|
51
|
+
- [ ] Thay đổi trực tiếp (Mutate) vào props.
|
|
52
|
+
- [ ] Thiếu `key` trong mảng, hoặc lấy `index` làm key cho các mảng có khả năng sắp xếp lại (reorder).
|
|
53
|
+
|
|
54
|
+
## Python
|
|
55
|
+
|
|
56
|
+
- [ ] Dùng mutable data làm giá trị mặc định (`def f(x=[])`).
|
|
57
|
+
- [ ] Dùng `except:` trần trụi (sẽ bắt nhầm luôn cả `KeyboardInterrupt` và `SystemExit`).
|
|
58
|
+
- [ ] Chia sẻ thuộc tính (Shared mutable attributes) sai cách ở cấp độ Class (`class C: items = []`).
|
|
59
|
+
- [ ] Dùng `is` thay vì `==` để so sánh giá trị (value comparison).
|
|
60
|
+
- [ ] Quên khai báo tham số `self` trong các phương thức của Class.
|
|
61
|
+
- [ ] Thay đổi danh sách (Modify list) trong khi đang duyệt (iterating).
|
|
62
|
+
- [ ] Cộng chuỗi trong vòng lặp (Khuyến khích dùng `"".join()`).
|
|
63
|
+
- [ ] Quên đóng file (Nên dùng câu lệnh `with`).
|
|
64
|
+
- [ ] Thiếu Type annotations (khai báo kiểu) ở các public functions.
|
|
65
|
+
|
|
66
|
+
## Go
|
|
67
|
+
|
|
68
|
+
- [ ] Phớt lờ lỗi (`result, _ := SomeFunction()`).
|
|
69
|
+
- [ ] Chạy Goroutine mà không có cơ chế thoát (Gây rò rỉ bộ nhớ).
|
|
70
|
+
- [ ] Quên truyền, hoặc truyền sai `context.Context`.
|
|
71
|
+
- [ ] Lỗi Capture biến của vòng lặp (Ở các bản Go < 1.22).
|
|
72
|
+
- [ ] Gọi `defer` ở trong vòng lặp (Chỉ được thực thi khi kết thúc hàm, KHÔNG PHẢI kết thúc vòng lặp).
|
|
73
|
+
- [ ] Khai báo đè biến (Variable shadowing).
|
|
74
|
+
- [ ] Dùng Map trước khi khởi tạo (`make`).
|
|
75
|
+
- [ ] Bọc lỗi (Error wrapping) bằng `%v` thay vì `%w` (Làm hỏng `errors.Is`/`errors.As`).
|
|
76
|
+
|
|
77
|
+
## Java / Spring Boot
|
|
78
|
+
|
|
79
|
+
- [ ] Viết dài dòng cho POJO/DTO thay vì dùng `record` *(Java 17+)*.
|
|
80
|
+
- [ ] Quên `break` trong switch truyền thống (Nên dùng switch expressions) *(Java 14+)*.
|
|
81
|
+
- [ ] Dùng Field injection (`@Autowired` thẳng vào biến) thay vì Constructor injection.
|
|
82
|
+
- [ ] Lỗi N+1 query trong JPA (Quên `fetch join` hoặc `@EntityGraph`).
|
|
83
|
+
- [ ] Gọi `Optional.get()` mà không chịu check `isPresent()` trước.
|
|
84
|
+
- [ ] Stream operations có chứa side effects.
|
|
85
|
+
|
|
86
|
+
## PHP
|
|
87
|
+
|
|
88
|
+
- [ ] Thiếu `declare(strict_types=1);` ở đầu file mới.
|
|
89
|
+
- [ ] So sánh yếu (`==`, `!=`) ở những chỗ nhạy cảm như login, token, thanh toán.
|
|
90
|
+
- [ ] Dùng `in_array()` / `array_search()` mà không bật chế độ nghiêm ngặt (strict mode).
|
|
91
|
+
- [ ] Nối chuỗi để build câu lệnh SQL (Dễ dính SQL Injection) thay vì dùng Prepared statements.
|
|
92
|
+
- [ ] In dữ liệu user (echo) mà không escape (Dễ dính XSS).
|
|
93
|
+
- [ ] Lưu password bằng `md5()` / `sha1()` thay vì `password_hash()`.
|
|
94
|
+
- [ ] Che giấu lỗi bằng dấu `@` hoặc nuốt lỗi bằng `catch` để trống.
|
|
95
|
+
|
|
96
|
+
## SQL
|
|
97
|
+
|
|
98
|
+
- [ ] Nối chuỗi để tạo query (Rủi ro SQL Injection) — luôn dùng Parameterized queries.
|
|
99
|
+
- [ ] Bỏ quên Index ở những cột hay bị filter/join.
|
|
100
|
+
- [ ] Dùng `SELECT *` thay vì chọn các cột cụ thể.
|
|
101
|
+
- [ ] Mẫu truy vấn N+1 (N+1 query patterns).
|
|
102
|
+
- [ ] Truy vấn bảng lớn mà không có `LIMIT`.
|
|
103
|
+
- [ ] So sánh `NULL` sai cách (`IS NULL` vs `= NULL`).
|
|
104
|
+
- [ ] Quên bao bọc các thao tác liên quan vào trong một Transaction.
|
|
105
|
+
- [ ] Chọn sai loại JOIN.
|
|
106
|
+
|
|
107
|
+
## Thiết kế API (API Design)
|
|
108
|
+
|
|
109
|
+
- [ ] Đặt tên endpoint (resource) thiếu nhất quán.
|
|
110
|
+
- [ ] Chọn sai HTTP methods (Ví dụ: Dùng POST cho một thao tác Idempotent thay vì PUT/PATCH).
|
|
111
|
+
- [ ] API lấy danh sách mà không có phân trang (pagination).
|
|
112
|
+
- [ ] Trả về sai HTTP Status codes.
|
|
113
|
+
- [ ] Không giới hạn số lượng request (Thiếu Rate limiting).
|
|
114
|
+
- [ ] Thiếu kiểm duyệt (validation) và làm sạch dữ liệu đầu vào.
|
|
115
|
+
- [ ] Chỉ tin tưởng hoàn toàn vào sự kiểm duyệt từ phía Client.
|
|
116
|
+
|
|
117
|
+
## Testing
|
|
118
|
+
|
|
119
|
+
- [ ] Chỉ chăm chăm test các chi tiết bên trong (Implementation details) thay vì test hành vi (Behavior) hiển thị ra bên ngoài.
|
|
120
|
+
- [ ] Bỏ sót các trường hợp Edge case (trường hợp biên).
|
|
121
|
+
- [ ] Test chạy lúc pass lúc fail (Flaky tests / Non-deterministic).
|
|
122
|
+
- [ ] Test chạy thẳng vào External dependencies (Thiếu mocks).
|
|
123
|
+
- [ ] Thiếu test cho các trường hợp báo lỗi (Negative tests).
|
|
124
|
+
- [ ] Setup bài test quá phức tạp.
|