10 KiB
10 KiB
代码审查清单 (Code Review Checklist)
按语言和框架分类的审查要点,以及 PR 审查流程 SOP。
TypeScript/JavaScript 审查清单
类型安全
- 禁止裸
any:所有any必须有注释说明原因,或替换为具体类型 /unknown - 类型断言最小化:
as断言需要注释理由;优先使用类型守卫 (type guard) - 泛型正确性:泛型参数有约束 (
extends),不使用无约束的<T> - 严格模式兼容:
strictNullChecks下无隐式undefined访问 - 联合类型穷举:
switch对联合类型使用exhaustive check(never 兜底) - 外部数据验证:API 响应、用户输入使用 Zod / io-ts 运行时校验
// ❌ 裸 any,无校验
function handleResponse(data: any) {
return data.user.name;
}
// ✅ 运行时校验 + 类型推导
const ResponseSchema = z.object({
user: z.object({ name: z.string() }),
});
function handleResponse(raw: unknown) {
const data = ResponseSchema.parse(raw);
return data.user.name; // 类型安全
}
异步安全
- Promise 错误处理:每个
await都在try-catch中,或链式.catch() - 竞态条件:组件卸载后不更新 state(AbortController / cleanup function)
- 并发控制:批量请求使用
Promise.allSettled而非Promise.all(避免一个失败全部丢弃) - 超时处理:网络请求设置合理的 timeout
- 重试机制:幂等操作配备指数退避 (exponential backoff)
// ❌ 竞态条件:组件卸载后仍设置 state
useEffect(() => {
fetchData().then(data => setData(data));
}, [id]);
// ✅ 使用 AbortController 取消
useEffect(() => {
const controller = new AbortController();
fetchData(id, { signal: controller.signal })
.then(data => setData(data))
.catch(err => {
if (!controller.signal.aborted) reportError(err);
});
return () => controller.abort();
}, [id]);
React 特定
- Hooks 依赖数组:
useEffect/useMemo/useCallback依赖完整且无多余项 - key 属性:列表渲染使用稳定唯一 key,禁止用 index 作 key(排序/删除场景)
- 不必要的 re-render:大组件拆分,使用
React.memo/useMemo防止子树重渲染 - 状态提升 vs 下沉:state 放在最近的共同祖先,不过度提升到顶层
- Context 粒度:避免巨大 Context 导致不相关组件重渲染
- 受控 vs 非受控:表单组件模式一致,不混用
Next.js 特定
- Server / Client 边界:
'use client'声明仅在必要时添加,Server Component 优先 - 数据获取位置:Server Component 中直接 fetch,不在 Client Component 中请求可在服务端获取的数据
- 缓存策略:
fetch的cache/revalidate选项合理;理解 ISR vs SSR vs SSG - Metadata:页面导出
metadata或generateMetadata以支持 SEO - Loading / Error 边界:
loading.tsx和error.tsx文件完整 - Route Handler 安全:API route 有鉴权中间件,参数经过校验
Python 审查清单
类型标注
- typing 模块使用:函数签名完整标注参数和返回值类型
- Pydantic model 验证:API 入参使用 Pydantic BaseModel,字段有
Field约束 - Optional 显式标注:可为 None 的字段使用
Optional[T]或T | None - 泛型容器:使用
list[str]而非List(Python 3.9+) - TypeAlias:复杂类型定义使用
TypeAlias提高可读性
# ❌ 无类型标注,无验证
def create_user(data):
return db.execute(f"INSERT INTO users VALUES ({data['name']})")
# ✅ Pydantic 验证 + 类型标注
class CreateUserRequest(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
email: EmailStr
async def create_user(request: CreateUserRequest) -> UserResponse:
return await user_service.create(request)
FastAPI 特定
- 依赖注入:数据库 session、当前用户等通过
Depends()注入 - 错误处理:使用
HTTPException返回合适状态码;全局异常处理器兜底 - 中间件链:CORS、日志、限流中间件顺序正确
- 路由组织:使用
APIRouter按模块分组;路径命名 RESTful - 响应模型:
response_model显式声明,避免泄露内部字段 - 后台任务:耗时操作使用
BackgroundTasks或 Celery
异步安全
- asyncio 使用:不在 async 函数中调用同步阻塞 I/O(使用
run_in_executor) - 连接池管理:数据库连接池大小合理;async session 正确关闭
- 并发限制:使用
asyncio.Semaphore控制并发数 - 上下文变量:使用
contextvars而非全局变量传递请求级数据
Go 审查清单
错误处理
- error wrapping:使用
fmt.Errorf("context: %w", err)包装错误,保留链路 - sentinel errors:预定义错误使用
errors.New并以Err前缀命名 - 自定义 error:复杂错误实现
Error()接口,携带结构化上下文 - errors.Is / As:错误判断使用
errors.Is而非== - 不忽略错误:
_ = someFunc()必须有注释说明原因
// ❌ 错误被忽略,无上下文
data, _ := json.Marshal(user)
db.Exec("INSERT INTO users VALUES (?)", data)
// ✅ 错误包装,逐层传递
data, err := json.Marshal(user)
if err != nil {
return fmt.Errorf("序列化用户数据失败: %w", err)
}
if _, err := db.Exec("INSERT INTO users VALUES (?)", data); err != nil {
return fmt.Errorf("插入用户记录失败: %w", err)
}
并发安全
- goroutine 泄漏:每个 goroutine 有明确退出条件(context cancel / done channel)
- mutex 使用:共享数据用
sync.Mutex或sync.RWMutex保护;锁粒度合理 - channel 模式:有缓冲 vs 无缓冲选择合理;
select配default或context.Done() - sync.WaitGroup:
Add在 goroutine 外调用;Done使用defer - data race:运行
go test -race检测竞态
Gin 特定
- 中间件:鉴权、日志、Recovery 中间件顺序正确
- 参数绑定:使用
ShouldBindJSON/ShouldBindQuery而非手动取值 - 响应格式:统一使用
c.JSON()返回标准格式{ code, message, data } - 路由分组:
v1 := r.Group("/api/v1")按版本和模块分组 - 优雅关停:使用
signal.Notify+srv.Shutdown(ctx)处理退出
PR 审查流程 SOP
审查前准备
- 理解上下文:阅读 PR 描述和关联 issue,明确变更目的
- 检查 CI 状态:确认自动化测试通过、lint 无报错
- 浏览变更范围:先看 file changes 概览,判断影响面
- 本地检出(可选):复杂变更建议本地运行验证
审查步骤(按优先级)
Step 1: 架构审查
- 变更是否符合系统架构设计?
- 模块职责划分是否合理?
- 依赖方向是否正确(不反向依赖)?
Step 2: 逻辑正确性
- 业务逻辑是否正确?边界条件是否覆盖?
- 数据流转是否完整?状态管理是否一致?
Step 3: 安全检查
- 输入是否经过验证和清理?
- 是否存在注入、XSS、CSRF 风险?
- 敏感数据是否加密 / 脱敏?
Step 4: 性能评估
- 是否引入 N+1 查询、大数据量循环?
- 缓存策略是否合理?
- 数据库索引是否匹配新查询?
Step 5: 代码风格
- 命名是否清晰、符合团队约定?
- 代码组织是否合理?是否过度抽象?
反馈规范
- 区分严重度:每条反馈标注级别(Blocker / Warning / Suggestion / Nit)
- 提供替代方案:指出问题的同时给出建议的修改代码
- 解释原因:说明为什么需要修改,而不是只说"改一下"
- 肯定优点:发现好的设计和实现也要给予正面反馈
- 对事不对人:审查代码而非审查人,使用"这段代码"而非"你的代码"
严重度分类标准
| 级别 | 标记 | 定义 | 示例 | 处理要求 |
|---|---|---|---|---|
| Critical | [BLOCKER] |
必须修复才能合并。存在正确性、安全性或数据完整性问题 | SQL 注入漏洞;竞态条件导致数据不一致;未处理的空指针异常 | 必须修复后重新审查 |
| Warning | [WARNING] |
强烈建议修复。存在性能隐患、可维护性问题或潜在 Bug | N+1 查询问题;缺少错误处理;过大的函数需要拆分 | 应当修复,可协商延期 |
| Suggestion | [SUGGESTION] |
改进建议。提升代码质量但不影响功能 | 可以使用更清晰的命名;建议提取公共方法;增加类型约束 | 可选,鼓励采纳 |
| Nit | [NIT] |
细微问题。风格偏好或微小优化 | 空行数量;import 排序;注释措辞 | 可选,不阻塞合并 |
审查反馈模板
**[BLOCKER]** SQL 注入风险
- 文件: `src/services/user.ts:42`
- 问题: 用户输入直接拼接到 SQL 查询字符串
- 建议:
```typescript
// 使用参数化查询
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
[SUGGESTION] 建议提取复用函数
- 文件:
src/utils/format.ts:15-28 - 问题: 格式化逻辑在多处重复
- 建议: 提取为
formatCurrency()工具函数
---
## 审查自检清单(审查者使用)
### 提交审查前自检
- [ ] 是否审查了所有变更文件?
- [ ] 是否理解了变更的业务目的?
- [ ] Critical 问题是否都已标注?
- [ ] 反馈是否具有可操作性(有建议、有代码)?
- [ ] 是否检查了测试覆盖?
- [ ] 是否有正面反馈?