
Dev Workflow
- 141 installs
- 2.5k repo stars
- Updated July 19, 2026
- xstongxue/best-skills
Use dev-workflow to assist with backend
About
dev-workflow is a developer tool for development projects. It helps with backend tasks.
- dev-workflow
- Backend & APIs
Dev Workflow by the numbers
- 141 all-time installs (skills.sh)
- +3 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,602 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xstongxue/best-skills --skill dev-workflowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 141 |
|---|---|
| repo stars | ★ 2.5k |
| Last updated | July 19, 2026 |
| Repository | xstongxue/best-skills ↗ |
What it does
Use dev-workflow to assist with backend
Files
开发流程五步法
需求理解 → 方案设计 → 代码实现 → 代码审查 → Bug 修复
使用时机
- 用户描述新功能/项目想法,需要需求分析
- 用户提到「方案设计」「架构设计」「怎么实现」
- 用户提到「代码实现」「开始写代码」「帮我实现」
- 用户提到「代码审查」「Review」「检查代码」「看看有没有问题」
- 用户提到「bug」「报错」「崩溃」「异常」「不工作」「出错了」「测试失败」
Step 1:识别当前步骤
根据用户需求选择对应 reference 文件执行:
| 步骤 | 文件 | 触发关键词 |
|---|---|---|
| 需求理解 | requirement.md | 需求分析、理解需求、整理需求、帮我梳理 |
| 方案设计 | design.md | 方案设计、技术设计、架构设计、怎么实现 |
| 代码实现 | implementation.md | 代码实现、开始写代码、帮我实现、写一下 |
| 代码审查 | review.md | 代码审查、Review、检查代码、看看有没有问题 |
| Bug 修复 | bug-fix.md | bug、报错、崩溃、异常、不工作、出错了、测试失败 |
Step 2:收集输入
- 需求理解:从用户描述或对话中提取功能想法、约束条件
- 方案设计:确认已有需求文档,或简要收集关键信息
- 代码实现:确认已有技术方案,或根据需求快速拟定实现思路
- 代码审查:明确审查范围(哪些文件/模块)
- Bug 修复:确认错误信息、复现步骤、环境信息
Step 3:执行、输出与自动落盘
读取对应 reference 中的完整流程,按步骤执行,输出符合该阶段要求的交付物。
其中以下步骤必须自动写入文档:
- 需求理解:
- 单模块/未指定模块:将最终需求文档追加写入当前工作目录的
docs/需求理解.md - 多模块且已识别模块名:将最终需求文档追加写入
docs/<module>/需求理解.md - 方案设计:
- 单模块/未指定模块:将最终方案文档追加写入当前工作目录的
docs/方案设计.md - 多模块且已识别模块名:将最终方案文档追加写入
docs/<module>/方案设计.md - 代码审查:
- 单模块/未指定模块:将最终审查报告追加写入当前工作目录的
docs/代码审查.md - 多模块且已识别模块名:将最终审查报告追加写入
docs/<module>/代码审查.md
落盘规则:
- 默认目录为当前工作目录
docs/,若不存在则创建后再写入 - 多模块项目优先使用
docs/<module>/分目录;若无法确定模块则回退到docs/根目录 <module>使用稳定标识(建议用目录名/包名,避免同义词)- 文件不存在则创建,存在则追加,不覆盖历史内容
- 每次新增内容前写入时间标题:
## YYYY-MM-DD HH:mm - 标题后粘贴本次完整输出正文,末尾加分隔线
--- - 若用户明确指定了其他文件路径,优先按用户指定路径写入
- 可选维护
docs/模块索引.md记录模块名与文档路径映射,便于检索与归档
注意事项
- 流程串联:需求理解 → 方案设计 → 代码实现 → 代码审查 → Bug 修复;每步完成后提示用户进入下一阶段
- 上游缺失时:提示用户先完成前置步骤,或简要收集关键信息后继续
- 需求理解、方案设计与代码审查阶段默认必须自动落盘(追加写入),不要覆盖历史记录
Bug 修复
自动化 Bug 修复流程:先分析问题,生成按优先级排序的多种修复方案,然后依次尝试,成功即停止。
使用时机
- 用户提到「bug」「报错」「崩溃」「异常」「不工作」「出错了」
- 用户粘贴错误堆栈、截图或描述非预期行为
- 代码跑不通、测试失败、功能异常
流程定位
需求分析 → 功能设计 → 功能实现 → 代码审查 → [Bug 修复]
↑ 当前---
Step 1:复现与定位
在提出方案前,先搜集足够信息:
1. 确认错误信息:完整的报错堆栈、错误码、截图 2. 确认复现步骤:什么操作触发了问题 3. 确认环境信息:语言版本、依赖版本、平台 4. 定位出错范围:读相关代码,找到可疑位置
若信息不足,先向用户补充提问,不要盲目猜测。
---
Step 2:生成修复方案列表
分析完成后,一次性列出所有可能的修复方案,按可能性从高到低排序:
## 修复方案(按优先级排序)
| 优先级 | 方案 | 原因 / 依据 | 风险 |
|--------|------|------------|------|
| 1 | [方案描述] | [为什么最可能] | 低/中/高 |
| 2 | [方案描述] | [依据] | 低/中/高 |
| 3 | [方案描述] | [依据] | 低/中/高 |排序原则:
- 优先无副作用的小改动(改配置、修参数、加判断)
- 其次局部代码逻辑修改
- 再次架构/依赖变更
- 最后大范围重构
---
Step 3:自动依次尝试,解决即停
从优先级最高的方案开始执行,每次只执行一个方案:
[尝试方案 1]
→ 应用修改
→ 验证(运行测试 / 复现步骤 / lint 检查)
→ 成功?→ 停止,报告结果
→ 失败?→ 回滚此方案,继续方案 2
[尝试方案 2]
→ 同上...
[全部失败]
→ 报告每个方案的失败原因
→ 请求用户提供更多信息验证方式(按情况选择):
- 运行已有测试:
npm test/pytest/go test ./... - 执行 lint 检查:
eslint/ruff/golangci-lint - 手动复现步骤:按用户描述的触发路径验证
- 检查关键日志输出
---
Step 4:报告结果
修复成功后输出:
## 修复结果
**根本原因**:[一句话说明问题本质]
**采用方案**:方案 X — [方案名称]
**修改内容**:
- `path/to/file.ext`:[改了什么]
**验证方式**:[如何确认已修复]
**其余方案未采用原因**(可选):
- 方案 2:[跳过原因]---
注意事项
- 每次只改一个方案,不要同时混合多个修复,否则无法判断是哪个生效
- 失败必须回滚,保持代码在干净状态后再试下一个
- 不要过度修复:修复范围限于问题本身,不顺手重构无关代码
- 若所有方案均失败,诚实告知,并说明需要哪些额外信息
方案设计
开发流程第二步。基于需求文档设计技术方案,包括架构设计、模块划分、技术选型与接口定义。
使用时机
- 用户提到「方案设计」「技术设计」「架构设计」「怎么实现」
- 用户已完成需求分析,准备进入设计阶段
- 作为开发流程的第二步被调用
Step 1:需求回顾
首先确认已有需求文档:
- 如有,快速回顾核心功能点与约束条件
- 如无,提示用户先完成需求分析,或简要收集关键信息
Step 2:技术选型
根据需求约束选择合适的技术栈:
选型维度
- 语言/框架:根据项目类型、团队熟悉度、生态成熟度选择
- 数据存储:根据数据结构、查询模式、一致性要求选择
- 第三方服务:根据功能需求评估是否引入外部 API 或库
选型原则
- 优先使用项目已有的技术栈,避免引入不必要的复杂度
- 给出选型理由,必要时提供备选方案对比
Step 3:架构设计
设计系统的整体结构:
分层架构(适用于大多数项目)
- 表现层:用户交互、界面渲染
- 业务层:核心逻辑、流程控制
- 数据层:数据存储、外部接口
模块划分
- 按功能职责划分模块,明确各模块边界
- 定义模块间的依赖关系与通信方式
关键设计决策
- 列出需要权衡的设计点(如同步 vs 异步、轮询 vs 推送)
- 给出选择及理由
Step 4:接口设计
定义模块间或前后端的接口契约:
接口定义模板
接口名称:xxx
请求方式:GET/POST/...
请求路径:/api/xxx
请求参数:
- param1 (类型, 必填/可选): 说明
- param2 (类型, 必填/可选): 说明
响应格式:
{
"code": 0,
"data": { ... }
}
错误码:
- 1001: 参数错误
- 1002: 权限不足Step 5:数据模型设计
定义核心数据结构:
数据库表设计(如适用)
表名:xxx
字段:
- id (int, 主键): 唯一标识
- name (varchar): 名称
- created_at (datetime): 创建时间
索引:name
关联:外键关联 yyy 表核心数据结构(类/接口定义)
class/interface XxxModel {
field1: type // 说明
field2: type // 说明
}Step 6:输出技术方案
按以下模板输出方案文档:
## 技术方案概述
[一段话描述整体技术思路]
## 技术选型
| 类别 | 选择 | 理由 |
|------|------|------|
| 语言 | xxx | xxx |
| 框架 | xxx | xxx |
| 数据库 | xxx | xxx |
## 架构设计
[架构分层说明,可配文字描述或伪架构图]
## 模块划分
| 模块 | 职责 | 依赖 |
|------|------|------|
| ModuleA | xxx | - |
| ModuleB | xxx | ModuleA |
## 接口设计
### 接口1:xxx
[接口定义]
### 接口2:xxx
[接口定义]
## 数据模型
### 表/类1:xxx
[字段定义]
## 关键设计决策
| 决策点 | 选择 | 理由 |
|--------|------|------|
| xxx | xxx | xxx |
## 实现计划
1. 第一步:实现 xxx
2. 第二步:实现 xxx
3. 第三步:集成测试Step 7:确认、交接与自动落盘
- 将技术方案呈现给用户,请求确认
- 标注任何风险点或待讨论的设计决策
- 将本次最终内容自动追加写入文档:
- 单模块/未指定模块:写入
docs/方案设计.md - 多模块且已识别模块名:写入
docs/<module>/方案设计.md - 默认目录为
docs/,若目录不存在则先创建 - 多模块优先使用
docs/<module>/分目录;若无法确定模块则回退到docs/根目录 <module>使用稳定标识(建议用目录名/包名,避免同义词)- 文件不存在则创建,存在则追加
- 写入时间标题
## YYYY-MM-DD HH:mm - 粘贴完整正文
- 末尾写入
--- - 完成后提示:「方案已设计完成,可使用
代码实现进入下一阶段」
注意事项
- 方案设计应与需求规模匹配,小功能不必过度设计
- 优先考虑简单方案,避免过早优化
- 如需求不明确,应返回需求阶段澄清,而非自行假设
- 流程串联:需求理解 → 方案设计 → 代码实现 → 代码审查;上游来自需求理解,下游交付代码实现
代码实现
开发流程第三步。基于技术方案进行编码,遵循最佳实践,输出可运行的代码。
使用时机
- 用户提到「代码实现」「开始写代码」「帮我实现」「写一下」
- 用户已完成方案设计,准备进入编码阶段
- 作为开发流程的第三步被调用
流程串联
需求理解 → 方案设计 → [代码实现] → 代码审查 → Bug 修复
↑ 当前- 上游输入:来自方案设计的技术方案
- 下游输出:交付给代码审查的实现代码
工作流程
Step 1:方案回顾与任务拆分
首先确认已有技术方案:
- 如有,回顾模块划分与实现计划
- 如无,提示用户先完成方案设计,或根据需求快速拟定实现思路
将实现任务拆分为可独立完成的子任务:
- 按模块或功能点拆分
- 确定实现顺序(先核心后辅助、先底层后上层)
Step 2:环境与依赖准备
在编码前确认:
- 项目结构是否已创建
- 依赖包是否已安装
- 配置文件是否已准备
如需创建项目或安装依赖,先完成这些准备工作。
Step 3:逐模块实现
按照拆分的任务逐一实现,每个模块遵循以下原则:
编码规范
- 遵循项目已有的代码风格(如有)
- 命名清晰,函数/变量名能表达用途
- 单一职责,每个函数只做一件事
- 适度注释,复杂逻辑需加注释说明
代码组织
- 按功能或层次组织文件结构
- 相关代码放在一起,减少跨文件跳转
- 公共逻辑抽取为工具函数或类
错误处理
- 对外部输入进行校验
- 使用 try-catch 处理可能的异常
- 提供有意义的错误信息
Step 4:关键代码实现模式
数据层实现
- 定义数据模型/表结构
- 实现 CRUD 操作
- 处理数据库连接与事务
业务层实现
- 实现核心业务逻辑
- 调用数据层获取/存储数据
- 处理业务异常与边界情况
接口层实现
- 定义路由与请求处理
- 参数校验与格式化
- 调用业务层并返回响应
前端实现(如适用)
- 组件划分与状态管理
- 接口调用与数据绑定
- 交互逻辑与样式实现
Step 5:代码输出格式
输出代码时遵循以下格式:
## 文件:path/to/file.ext
[代码内容]
---
## 文件:path/to/another.ext
[代码内容]对于每个文件:
- 说明文件用途
- 标注关键实现点
- 如有复杂逻辑,附带简要说明
Step 6:自测与验证
代码实现后进行基本验证:
- 语法检查:确保代码无语法错误
- 逻辑检查:核心路径是否能走通
- 边界检查:异常输入是否能正确处理
如发现问题,及时修复后再交付。
Step 7:交付与交接
- 汇总所有实现的代码文件
- 说明如何运行/测试
- 列出已知的限制或待优化点
- 提示:「代码已实现完成,可使用
代码审查进行质量检查」
实现清单模板
在开始实现前,可输出实现清单供用户确认:
## 实现清单
### 待创建文件
- [ ] path/to/file1.ext - 用途说明
- [ ] path/to/file2.ext - 用途说明
### 待修改文件
- [ ] path/to/existing.ext - 修改说明
### 实现顺序
1. 先实现 xxx(基础模块)
2. 再实现 xxx(依赖上一步)
3. 最后实现 xxx(集成)
确认后开始实现?注意事项
- 优先使用项目已有的工具函数和组件,避免重复造轮子
- 代码量较大时分批输出,每批完成后等待用户确认
- 如实现过程中发现方案有问题,及时反馈而非硬着头皮写
- 保持代码简洁,不要过度设计或提前优化
需求理解与分析
开发流程第一步。将用户的模糊需求转化为结构化需求文档。
使用时机
- 用户描述一个新功能或新项目的想法
- 用户提到「需求分析」「理解需求」「整理需求」「帮我梳理一下」
- 作为开发流程的起点被调用
Step 1:信息收集
从用户输入中提取以下信息(如缺失则主动追问):
- 核心目标:用户想要解决什么问题?达成什么效果?
- 使用场景:谁会用?在什么情况下用?
- 输入输出:系统接收什么、产出什么?
- 约束条件:技术栈限制、性能要求、时间预算等
- 参考示例:有无类似产品或期望的交互方式?
Step 2:需求拆解
将收集到的信息拆解为结构化需求:
功能需求(必须实现)
- 列出核心功能点,每个功能点用一句话描述
- 标注优先级:P0(必须)/ P1(重要)/ P2(可选)
非功能需求(质量属性)
- 性能要求(响应时间、并发量等)
- 安全要求(权限、数据保护等)
- 可用性要求(容错、恢复等)
边界与约束
- 明确不做什么(Out of Scope)
- 技术栈或平台限制
- 依赖的外部系统或接口
Step 3:验收标准定义
为每个核心功能点定义可验证的验收标准:
- 使用「Given-When-Then」或「输入-操作-预期结果」格式
- 标准应具体、可测试,避免模糊描述
Step 4:输出需求文档
按以下模板输出结构化需求文档:
## 需求概述
[一段话描述项目/功能的核心目标与价值]
## 功能需求
### P0 - 必须实现
- [ ] 功能点1:描述
- [ ] 功能点2:描述
### P1 - 重要功能
- [ ] 功能点3:描述
### P2 - 可选功能
- [ ] 功能点4:描述
## 非功能需求
- 性能:xxx
- 安全:xxx
- 其他:xxx
## 边界与约束
- 不包含:xxx
- 技术限制:xxx
- 外部依赖:xxx
## 验收标准
### 功能点1
- Given [前置条件],When [操作],Then [预期结果]
### 功能点2
- Given [前置条件],When [操作],Then [预期结果]Step 5:确认、交接与自动落盘
- 将需求文档呈现给用户,请求确认
- 标注任何假设或待澄清的问题
- 将本次最终内容自动追加写入文档:
- 单模块/未指定模块:写入
docs/需求理解.md - 多模块且已识别模块名:写入
docs/<module>/需求理解.md - 默认目录为
docs/,若目录不存在则先创建 - 多模块优先使用
docs/<module>/分目录;若无法确定模块则回退到docs/根目录 <module>使用稳定标识(建议用目录名/包名,避免同义词)- 文件不存在则创建,存在则追加
- 写入时间标题
## YYYY-MM-DD HH:mm - 粘贴完整正文
- 末尾写入
--- - 完成后提示:「需求已整理完成,可使用
方案设计进入下一阶段」
注意事项
- 不要急于给出技术方案,本阶段聚焦于「做什么」而非「怎么做」
- 对于模糊描述,主动追问而非自行假设
- 需求文档应让非技术人员也能理解
- 流程串联:需求理解 → 方案设计 → 代码实现 → 代码审查;完成本步后建议用户使用方案设计进入下一阶段
架构审查指南
架构设计审查指南,帮助评估代码的架构是否合理、设计是否恰当。
SOLID 原则检查清单
S - 单一职责原则(SRP)
检查要点:
- 这个类/模块是否只有一个改变的理由?
- 类中的方法是否都服务于同一个目的?
- 如果要向非技术人员描述这个类,能否用一句话说清楚?
代码审查中的识别信号:
⚠️ 类名包含 "And"、"Manager"、"Handler"、"Processor" 等泛化词汇
⚠️ 一个类超过 200-300 行代码
⚠️ 类有超过 5-7 个公共方法
⚠️ 不同的方法操作完全不同的数据审查问题:
- "这个类负责哪些事情?能否拆分?"
- "如果 X 需求变化,哪些方法需要改?如果 Y 需求变化呢?"
O - 开闭原则(OCP)
检查要点:
- 添加新功能时,是否需要修改现有代码?
- 是否可以通过扩展(继承、组合)来添加新行为?
- 是否存在大量的 if/else 或 switch 语句来处理不同类型?
代码审查中的识别信号:
⚠️ switch/if-else 链处理不同类型
⚠️ 添加新功能需要修改核心类
⚠️ 类型检查 (instanceof, typeof) 散布在代码中审查问题:
- "如果要添加新的 X 类型,需要修改哪些文件?"
- "这个 switch 语句会随着新类型增加而增长吗?"
L - 里氏替换原则(LSP)
检查要点:
- 子类是否可以完全替代父类使用?
- 子类是否改变了父类方法的预期行为?
- 是否存在子类抛出父类未声明的异常?
代码审查中的识别信号:
⚠️ 显式类型转换 (casting)
⚠️ 子类方法抛出未实现异常(NotImplementedException)
⚠️ 子类方法为空实现或只有 return
⚠️ 使用基类的地方需要检查具体类型审查问题:
- "如果用子类替换父类,调用方代码是否需要修改?"
- "这个方法在子类中的行为是否符合父类的契约?"
I - 接口隔离原则(ISP)
检查要点:
- 接口是否足够小且专注?
- 实现类是否被迫实现不需要的方法?
- 客户端是否依赖了它不使用的方法?
代码审查中的识别信号:
⚠️ 接口超过 5-7 个方法
⚠️ 实现类有空方法或抛出 NotImplementedException
⚠️ 接口名称过于宽泛(如 IManager、IService)
⚠️ 不同的客户端只使用接口的部分方法审查问题:
- "这个接口的所有方法是否都被每个实现类使用?"
- "能否将这个大接口拆分为更小的专用接口?"
D - 依赖倒置原则(DIP)
检查要点:
- 高层模块是否依赖于抽象而非具体实现?
- 是否使用依赖注入而非直接 new 对象?
- 抽象是否由高层模块定义而非低层模块?
代码审查中的识别信号:
⚠️ 高层模块直接 new 低层模块的具体类
⚠️ 导入具体实现类而非接口/抽象类
⚠️ 配置和连接字符串硬编码在业务逻辑中
⚠️ 难以为某个类编写单元测试审查问题:
- "这个类的依赖能否在测试时被桩对象(mock)替换?"
- "如果要更换数据库/API 实现,需要修改多少地方?"
---
架构反模式识别
致命反模式
| 反模式 | 识别信号 | 影响 |
|---|---|---|
| 大泥球 (Big Ball of Mud) | 没有清晰的模块边界,任何代码都可能调用任何其他代码 | 难以理解、修改和测试 |
| 上帝类 (God Object) | 单个类承担过多职责,知道太多、做太多 | 高耦合,难以重用和测试 |
| 意大利面条代码 | 控制流程混乱,goto 或深层嵌套,难以追踪执行路径 | 难以理解和维护 |
| 熔岩流 (Lava Flow) | 没人敢动的古老代码,缺乏文档和测试 | 技术债务累积 |
设计反模式
| 反模式 | 识别信号 | 建议 |
|---|---|---|
| 金锤子 (Golden Hammer) | 对所有问题使用同一种技术/模式 | 根据问题选择合适的解决方案 |
| 过度工程(Gas Factory) | 简单问题用复杂方案解决,滥用设计模式 | YAGNI 原则,先简单后复杂 |
| 船锚 (Boat Anchor) | 为"将来可能需要"而写的未使用代码 | 删除未使用代码,需要时再写 |
| 复制粘贴编程 | 相同逻辑出现在多处 | 提取公共方法或模块 |
审查问题
🔴 [blocking] "这个类有 2000 行代码,建议拆分为多个专注的类"
🟡 [important] "这段逻辑在 3 个地方重复,考虑提取为公共方法?"
💡 [suggestion] "这个 switch 语句可以用策略模式替代,更易扩展"---
耦合度与内聚性评估
耦合类型(从好到差)
| 类型 | 描述 | 示例 |
|---|---|---|
| 消息耦合 ✅ | 通过参数传递数据 | calculate(price, quantity) |
| 数据耦合 ✅ | 共享简单数据结构 | processOrder(orderDTO) |
| 印记耦合 ⚠️ | 共享复杂数据结构但只用部分 | 传入整个 User 对象但只用 name |
| 控制耦合 ⚠️ | 传递控制标志影响行为 | process(data, isAdmin=true) |
| 公共耦合 ❌ | 共享全局变量 | 多个模块读写同一个全局状态 |
| 内容耦合 ❌ | 直接访问另一模块的内部 | 直接操作另一个类的私有属性 |
内聚类型(从好到差)
| 类型 | 描述 | 质量 |
|---|---|---|
| 功能内聚 | 所有元素完成单一任务 | ✅ 最佳 |
| 顺序内聚 | 输出作为下一步输入 | ✅ 良好 |
| 通信内聚 | 操作相同数据 | ⚠️ 可接受 |
| 时间内聚 | 同时执行的任务 | ⚠️ 较差 |
| 逻辑内聚 | 逻辑相关但功能不同 | ❌ 差 |
| 偶然内聚 | 没有明显关系 | ❌ 最差 |
度量指标参考
耦合指标:
CBO (类间耦合):
好: < 5
警告: 5-10
危险: > 10
Ce (传出耦合):
描述: 依赖多少外部类
好: < 7
Ca (传入耦合):
描述: 被多少类依赖
高值意味着: 修改影响大,需要稳定
内聚指标:
LCOM4 (方法缺乏内聚):
1: 单一职责 ✅
2-3: 可能需要拆分 ⚠️
>3: 应该拆分 ❌审查问题
- "这个模块依赖了多少其他模块?能否减少?"
- "修改这个类会影响多少其他地方?"
- "这个类的方法是否都操作相同的数据?"
---
分层架构审查
清晰架构(Clean Architecture)分层检查
┌─────────────────────────────────────┐
│ 外层框架与驱动层 │ ← 最外层:Web、DB、UI
├─────────────────────────────────────┤
│ 接口适配层 │ ← 控制器、网关、展示器
├─────────────────────────────────────┤
│ 应用层 │ ← 用例、应用服务
├─────────────────────────────────────┤
│ 领域层 │ ← 实体、领域服务
└─────────────────────────────────────┘
↑ 依赖方向只能向内 ↑依赖规则检查
核心规则:源代码依赖只能指向内层
// ❌ 违反依赖规则:领域层依赖基础设施层
// domain/User.ts
import { MySQLConnection } from '../infrastructure/database';
// ✅ 正确:领域层定义接口,基础设施层实现
// domain/UserRepository.ts (接口)
interface UserRepository {
findById(id: string): Promise<User>;
}
// infrastructure/MySQLUserRepository.ts (实现)
class MySQLUserRepository implements UserRepository {
findById(id: string): Promise<User> { /* ... */ }
}审查清单
层次边界检查:
- [ ] 领域层是否有外部依赖(数据库、HTTP、文件系统)?
- [ ] 应用层是否直接操作数据库或调用外部 API?
- [ ] 控制器是否包含业务逻辑?
- [ ] 是否存在跨层调用(UI 直接调用 Repository)?
关注点分离检查:
- [ ] 业务逻辑是否与展示逻辑分离?
- [ ] 数据访问是否封装在专门的层?
- [ ] 配置和环境相关代码是否集中管理?
审查问题
🔴 [blocking] "领域实体直接导入了数据库连接,违反依赖规则"
🟡 [important] "控制器包含业务计算逻辑,建议移到服务层"
💡 [suggestion] "考虑使用依赖注入来解耦这些组件"---
设计模式使用评估
何时使用设计模式
| 模式 | 适用场景 | 不适用场景 |
|---|---|---|
| Factory | 需要创建不同类型对象,类型在运行时确定 | 只有一种类型,或类型固定不变 |
| Strategy | 算法需要在运行时切换,有多种可互换的行为 | 只有一种算法,或算法不会变化 |
| Observer | 一对多依赖,状态变化需要通知多个对象 | 简单的直接调用即可满足需求 |
| Singleton | 确实需要全局唯一实例,如配置管理 | 可以通过依赖注入传递的对象 |
| Decorator | 需要动态添加职责,避免继承爆炸 | 职责固定,不需要动态组合 |
过度设计警告信号
⚠️ Patternitis(模式炎)识别信号:
1. 简单的 if/else 被替换为策略模式 + 工厂 + 注册表
2. 只有一个实现的接口
3. 为了"将来可能需要"而添加的抽象层
4. 代码行数因模式应用而大幅增加
5. 新人需要很长时间才能理解代码结构审查原则
✅ 正确使用模式:
- 解决了实际的可扩展性问题
- 代码更容易理解和测试
- 添加新功能变得更简单
❌ 过度使用模式:
- 为了使用模式而使用
- 增加了不必要的复杂度
- 违反了 YAGNI 原则审查问题
- "使用这个模式解决了什么具体问题?"
- "如果不用这个模式,代码会有什么问题?"
- "这个抽象层带来的价值是否大于它的复杂度?"
---
可扩展性评估
扩展性检查清单
功能扩展性:
- [ ] 添加新功能是否需要修改核心代码?
- [ ] 是否提供了扩展点(hooks、plugins、events)?
- [ ] 配置是否外部化(配置文件、环境变量)?
数据扩展性:
- [ ] 数据模型是否支持新增字段?
- [ ] 是否考虑了数据量增长的场景?
- [ ] 查询是否有合适的索引?
负载扩展性:
- [ ] 是否可以水平扩展(添加更多实例)?
- [ ] 是否有状态依赖(session、本地缓存)?
- [ ] 数据库连接是否使用连接池?
扩展点设计检查
// ✅ 好的扩展设计:使用事件/钩子
class OrderService {
private hooks: OrderHooks;
async createOrder(order: Order) {
await this.hooks.beforeCreate?.(order);
const result = await this.save(order);
await this.hooks.afterCreate?.(result);
return result;
}
}
// ❌ 差的扩展设计:硬编码所有行为
class OrderService {
async createOrder(order: Order) {
await this.sendEmail(order); // 硬编码
await this.updateInventory(order); // 硬编码
await this.notifyWarehouse(order); // 硬编码
return await this.save(order);
}
}审查问题
💡 [suggestion] "如果将来需要支持新的支付方式,这个设计是否容易扩展?"
🟡 [important] "这里的逻辑是硬编码的,考虑使用配置或策略模式?"
📚 [learning] "事件驱动架构可以让这个功能更容易扩展"---
代码结构最佳实践
目录组织
按功能/领域组织(推荐):
src/
├── user/
│ ├── User.ts (实体)
│ ├── UserService.ts (服务)
│ ├── UserRepository.ts (数据访问)
│ └── UserController.ts (API)
├── order/
│ ├── Order.ts
│ ├── OrderService.ts
│ └── ...
└── shared/
├── utils/
└── types/按技术层组织(不推荐):
src/
├── controllers/ ← 不同领域混在一起
│ ├── UserController.ts
│ └── OrderController.ts
├── services/
├── repositories/
└── models/命名约定检查
| 类型 | 约定 | 示例 |
|---|---|---|
| 类名 | PascalCase,名词 | UserService, OrderRepository |
| 方法名 | camelCase,动词 | createUser, findOrderById |
| 接口名 | I 前缀或无前缀 | IUserService 或 UserService |
| 常量 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
| 私有属性 | 下划线前缀或无 | _cache 或 #cache |
文件大小指南
建议限制:
单个文件: < 300 行
单个函数: < 50 行
单个类: < 200 行
函数参数: < 4 个
嵌套深度: < 4 层
超出限制时:
- 考虑拆分为更小的单元
- 使用组合而非继承
- 提取辅助函数或类审查问题
🟢 [nit] "这个 500 行的文件可以考虑按职责拆分"
🟡 [important] "建议按功能领域而非技术层组织目录结构"
💡 [suggestion] "函数名 `process` 不够明确,考虑改为 `calculateOrderTotal`?"---
快速参考清单
架构审查 5 分钟速查
□ 依赖方向是否正确?(外层依赖内层)
□ 是否存在循环依赖?
□ 核心业务逻辑是否与框架/UI/数据库解耦?
□ 是否遵循 SOLID 原则?
□ 是否存在明显的反模式?红旗信号(必须处理)
🔴 上帝类(God Object)- 单个类超过 1000 行
🔴 循环依赖 - A → B → C → A
🔴 领域层包含框架依赖
🔴 硬编码的配置和密钥
🔴 没有接口的外部服务调用黄旗信号(建议处理)
🟡 类间耦合度 (CBO) > 10
🟡 方法参数超过 5 个
🟡 嵌套深度超过 4 层
🟡 重复代码块 > 10 行
🟡 只有一个实现的接口---
工具推荐
| 工具 | 用途 | 语言支持 |
|---|---|---|
| SonarQube | 代码质量、耦合度分析 | 多语言 |
| NDepend | 依赖分析、架构规则 | .NET |
| JDepend | 包依赖分析 | Java |
| Madge | 模块依赖图 | JavaScript/TypeScript |
| ESLint | 代码规范、复杂度检查 | JavaScript/TypeScript |
| CodeScene | 技术债务、热点分析 | 多语言 |
---
参考资源
C 代码审查指南
面向 C 的代码审查指南,重点关注内存安全、未定义行为(UB)与可移植性。示例默认基于 C11。
目录
---
指针与缓冲区安全
缓冲区必须携带长度
// ❌ Bad: 忽略目标缓冲区大小
bool copy_name(char *dst, size_t dst_size, const char *src) {
strcpy(dst, src);
return true;
}
// ✅ Good: 校验长度并保证终止符
bool copy_name(char *dst, size_t dst_size, const char *src) {
size_t len = strlen(src);
if (len + 1 > dst_size) {
return false;
}
memcpy(dst, src, len + 1);
return true;
}避免危险 API
优先使用 snprintf、fgets 和显式边界检查,避免使用 gets、strcpy、sprintf。
// ❌ Bad: 无边界写入
sprintf(buf, "%s", input);
// ✅ Good: 有边界写入
snprintf(buf, buf_size, "%s", input);使用正确的拷贝原语
// ❌ Bad: 区域重叠时仍使用 memcpy
memcpy(dst, src, len);
// ✅ Good: memmove 可处理重叠
memmove(dst, src, len);---
所有权与资源管理
一次分配,一次释放
明确所有权,并在每条错误路径上都做清理。
// ✅ Good: cleanup 标签避免泄漏
int load_file(const char *path) {
int rc = -1;
FILE *f = NULL;
char *buf = NULL;
f = fopen(path, "rb");
if (!f) {
goto cleanup;
}
buf = malloc(4096);
if (!buf) {
goto cleanup;
}
if (fread(buf, 1, 4096, f) == 0) {
goto cleanup;
}
rc = 0;
cleanup:
free(buf);
if (f) {
fclose(f);
}
return rc;
}---
未定义行为陷阱
常见 UB 模式
// ❌ Bad: use after free
char *p = malloc(10);
free(p);
p[0] = 'a';
// ❌ Bad: 读取未初始化变量
int x;
if (x > 0) { /* UB */ }
// ❌ Bad: 有符号整数溢出
int sum = a + b;避免越过对象边界的指针运算
// ❌ Bad: 指针先越界再解引用
int arr[4];
int *p = arr + 4;
int v = *p; // UB---
整数类型与溢出
避免有符号/无符号转换陷阱
// ❌ Bad: 负数被转换为巨大的 size_t
int len = -1;
size_t n = len;
// ✅ Good: 先校验再转换
if (len < 0) {
return -1;
}
size_t n = (size_t)len;在大小计算中检查溢出
// ❌ Bad: 乘法可能溢出
size_t bytes = count * sizeof(Item);
// ✅ Good: 乘法前先检查
if (count > SIZE_MAX / sizeof(Item)) {
return NULL;
}
size_t bytes = count * sizeof(Item);---
错误处理
始终检查返回值
// ❌ Bad: 忽略错误
fread(buf, 1, size, f);
// ✅ Good: 正确处理错误
size_t read = fread(buf, 1, size, f);
if (read != size && ferror(f)) {
return -1;
}统一错误契约
- 使用明确约定:成功返回 0,失败返回负值。
- 说明成功与失败分支下的所有权规则。
- 若使用
errno,仅在真实失败时设置。
---
并发
volatile 不是同步原语
// ❌ Bad: 数据竞争
volatile int stop = 0;
void worker(void) {
while (!stop) { /* ... */ }
}
// ✅ Good: 使用 C11 原子类型
_Atomic int stop = 0;
void worker(void) {
while (!atomic_load(&stop)) { /* ... */ }
}共享状态应使用互斥锁
共享数据应由 pthread_mutex_t 或同类机制保护。避免持锁执行 I/O。
---
宏与预处理器
宏参数要加括号
// ❌ Bad: 宏遇到副作用参数会出问题
#define MIN(a, b) ((a) < (b) ? (a) : (b))
int x = MIN(i++, j++);
// ✅ Good: 优先 static inline 函数
static inline int min_int(int a, int b) {
return a < b ? a : b;
}---
API 设计与 const
const 正确性与长度参数
// ✅ Good: 输入 const + 显式长度
int hash_bytes(const uint8_t *data, size_t len, uint8_t *out);明确空指针语义
要清晰说明指针是否允许为 NULL。可行时优先返回错误码,而不是返回 NULL。
---
工具链与构建检查
# 警告
clang -Wall -Wextra -Werror -Wconversion -Wshadow -std=c11 ...
# Sanitizer(调试构建)
clang -fsanitize=address,undefined -fno-omit-frame-pointer -g ...
clang -fsanitize=thread -fno-omit-frame-pointer -g ...
# 静态分析
clang-tidy src/*.c -- -std=c11
cppcheck --enable=warning,performance,portability src/
# 格式化
clang-format -i src/*.c include/*.h---
审查检查清单
内存与 UB
- [ ] 所有缓冲区接口都带显式长度参数
- [ ] 不存在越界访问或越过对象边界的指针运算
- [ ] 不存在 use after free 或未初始化读取
- [ ] 有符号溢出与位移规则被正确处理
API 与设计
- [ ] 所有权规则有文档且保持一致
- [ ] 输入参数遵循 const-correctness
- [ ] 错误契约清晰且一致
并发
- [ ] 共享状态无数据竞争
- [ ] 未将 volatile 用作同步机制
- [ ] 锁持有时间尽可能短
工具与测试
- [ ] 开启警告后可干净构建
- [ ] 关键路径已运行 Sanitizer
- [ ] 静态分析告警已处理
常见缺陷检查清单
按语言整理的代码审查高频缺陷与风险点。
通用问题
逻辑错误
- [ ] 循环或数组访问中的 off-by-one 错误
- [ ] 布尔逻辑错误(如德摩根定律误用)
- [ ] 缺失 null/undefined 判定
- [ ] 并发代码中的竞态条件
- [ ] 比较运算符使用错误(== vs ===,= vs ==)
- [ ] 整数溢出/下溢
- [ ] 浮点数比较问题
资源管理
- [ ] 内存泄漏(连接、监听器未释放)
- [ ] 文件句柄未关闭
- [ ] 数据库连接未释放
- [ ] 事件监听未移除
- [ ] 定时器/轮询未清理
错误处理
- [ ] 异常被吞掉(空 catch 块)
- [ ] 过度泛化异常处理掩盖具体错误
- [ ] 缺失错误向上传播
- [ ] 抛出的错误类型不正确
- [ ] 缺失 finally/清理逻辑
TypeScript/JavaScript
类型问题
// ❌ Using any defeats type safety
function process(data: any) { return data.value; }
// ✅ Use proper types
interface Data { value: string; }
function process(data: Data) { return data.value; }异步/Await 常见陷阱
// ❌ Missing await
async function fetch() {
const data = fetchData(); // Missing await!
return data.json();
}
// ❌ Unhandled promise rejection
async function risky() {
const result = await fetchData(); // No try-catch
return result;
}
// ✅ Proper error handling
async function safe() {
try {
const result = await fetchData();
return result;
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}React 专项
Hooks 规则违反
// ❌ 条件调用 Hooks — 违反 Hooks 规则
function BadComponent({ show }) {
if (show) {
const [value, setValue] = useState(0); // Error!
}
return <div>...</div>;
}
// ✅ Hooks 必须在顶层无条件调用
function GoodComponent({ show }) {
const [value, setValue] = useState(0);
if (!show) return null;
return <div>{value}</div>;
}
// ❌ 循环中调用 Hooks
function BadLoop({ items }) {
items.forEach(item => {
const [selected, setSelected] = useState(false); // Error!
});
}
// ✅ 将状态提升或使用不同的数据结构
function GoodLoop({ items }) {
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
return items.map(item => (
<Item key={item.id} selected={selectedIds.has(item.id)} />
));
}useEffect 常见错误
// ❌ 依赖数组不完整 — stale closure
function StaleClosureExample({ userId, onSuccess }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData(userId).then(result => {
setData(result);
onSuccess(result); // onSuccess 可能是 stale 的!
});
}, [userId]); // 缺少 onSuccess 依赖
}
// ✅ 完整的依赖数组
useEffect(() => {
fetchData(userId).then(result => {
setData(result);
onSuccess(result);
});
}, [userId, onSuccess]);
// ❌ 无限循环 — 在 effect 中更新依赖
function InfiniteLoop() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // 触发重渲染,又触发 effect
}, [count]); // 无限循环!
}
// ❌ 缺少清理函数 — 内存泄漏
function MemoryLeak({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser); // 组件卸载后仍然调用 setUser
}, [userId]);
}
// ✅ 正确的清理
function NoLeak({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetchUser(userId).then(data => {
if (!cancelled) setUser(data);
});
return () => { cancelled = true; };
}, [userId]);
}
// ❌ useEffect 用于派生状态(反模式)
function BadDerived({ items }) {
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((a, b) => a + b.price, 0));
}, [items]); // 不必要的 effect + 额外渲染
}
// ✅ 直接计算或用 useMemo
function GoodDerived({ items }) {
const total = useMemo(
() => items.reduce((a, b) => a + b.price, 0),
[items]
);
}
// ❌ useEffect 用于事件响应
function BadEvent() {
const [query, setQuery] = useState('');
useEffect(() => {
if (query) logSearch(query); // 应该在事件处理器中
}, [query]);
}
// ✅ 副作用在事件处理器中
function GoodEvent() {
const handleSearch = (q: string) => {
setQuery(q);
logSearch(q);
};
}useMemo / useCallback 误用
// ❌ 过度优化 — 常量不需要 memo
function OverOptimized() {
const config = useMemo(() => ({ api: '/v1' }), []); // 无意义
const noop = useCallback(() => {}, []); // 无意义
}
// ❌ 空依赖的 useMemo(可能隐藏 bug)
function EmptyDeps({ user }) {
const greeting = useMemo(() => `Hello ${user.name}`, []);
// user 变化时 greeting 不更新!
}
// ❌ useCallback 依赖总是变化
function UselessCallback({ data }) {
const process = useCallback(() => {
return data.map(transform);
}, [data]); // 如果 data 每次都是新引用,完全无效
}
// ❌ useMemo/useCallback 没有配合 React.memo
function Parent() {
const data = useMemo(() => compute(), []);
const handler = useCallback(() => {}, []);
return <Child data={data} onClick={handler} />;
// Child 没有用 React.memo,这些优化毫无意义
}
// ✅ 正确的优化组合
const MemoChild = React.memo(function Child({ data, onClick }) {
return <button onClick={onClick}>{data}</button>;
});
function Parent() {
const data = useMemo(() => expensiveCompute(), [dep]);
const handler = useCallback(() => {}, []);
return <MemoChild data={data} onClick={handler} />;
}组件设计问题
// ❌ 在组件内定义组件
function Parent() {
// 每次渲染都创建新的 Child 函数,导致完全重新挂载
const Child = () => <div>child</div>;
return <Child />;
}
// ✅ 组件定义在外部
const Child = () => <div>child</div>;
function Parent() {
return <Child />;
}
// ❌ Props 总是新引用 — 破坏 memo
function BadProps() {
return (
<MemoComponent
style={{ color: 'red' }} // 每次渲染新对象
onClick={() => handle()} // 每次渲染新函数
items={data.filter(x => x)} // 每次渲染新数组
/>
);
}
// ❌ 直接修改 props
function MutateProps({ user }) {
user.name = 'Changed'; // 永远不要这样做!
return <div>{user.name}</div>;
}Server Components 错误 (React 19+)
// ❌ 在 Server Component 中使用客户端 API
// app/page.tsx (默认是 Server Component)
export default function Page() {
const [count, setCount] = useState(0); // Error!
useEffect(() => {}, []); // Error!
return <button onClick={() => {}}>Click</button>; // Error!
}
// ✅ 交互逻辑移到 Client Component
// app/counter.tsx
'use client';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// app/page.tsx
import { Counter } from './counter';
export default async function Page() {
const data = await fetchData(); // Server Component 可以直接 await
return <Counter initialCount={data.count} />;
}
// ❌ 在父组件标记 'use client',整个子树变成客户端
// layout.tsx
'use client'; // 坏主意!所有子组件都变成客户端组件
export default function Layout({ children }) { ... }测试常见错误
// ❌ 使用 container 查询
const { container } = render(<Component />);
const button = container.querySelector('button'); // 不推荐
// ✅ 使用 screen 和语义查询
render(<Component />);
const button = screen.getByRole('button', { name: /submit/i });
// ❌ 使用 fireEvent
fireEvent.click(button);
// ✅ 使用 userEvent
await userEvent.click(button);
// ❌ 测试实现细节
expect(component.state.isOpen).toBe(true);
// ✅ 测试行为
expect(screen.getByRole('dialog')).toBeVisible();
// ❌ 等待同步查询
await screen.getByText('Hello'); // getBy 是同步的
// ✅ 异步用 findBy
await screen.findByText('Hello'); // findBy 会等待React 常见错误清单
- [ ] Hooks 不在顶层调用(条件/循环中)
- [ ] useEffect 依赖数组不完整
- [ ] useEffect 缺少清理函数
- [ ] useEffect 用于派生状态计算
- [ ] useMemo/useCallback 过度使用
- [ ] useMemo/useCallback 没配合 React.memo
- [ ] 在组件内定义子组件
- [ ] Props 是新对象/函数引用(传给 memo 组件时)
- [ ] 直接修改 props
- [ ] 列表缺少 key 或用 index 作为 key
- [ ] Server Component 使用客户端 API
- [ ] 'use client' 放在父组件导致整个树客户端化
- [ ] 测试使用 container 查询而非 screen
- [ ] 测试实现细节而非行为
React 19 Actions & Forms 错误
// === useActionState 错误 ===
// ❌ 在 Action 中直接 setState 而不是返回状态
const [state, action] = useActionState(async (prev, formData) => {
setSomeState(newValue); // 错误!应该返回新状态
}, initialState);
// ✅ 返回新状态
const [state, action] = useActionState(async (prev, formData) => {
const result = await submitForm(formData);
return { ...prev, data: result }; // 返回新状态
}, initialState);
// ❌ 忘记处理 isPending
const [state, action] = useActionState(submitAction, null);
return <button>Submit</button>; // 用户可以重复点击
// ✅ 使用 isPending 禁用按钮
const [state, action, isPending] = useActionState(submitAction, null);
return <button disabled={isPending}>Submit</button>;
// === useFormStatus 错误 ===
// ❌ 在 form 同级调用 useFormStatus
function Form() {
const { pending } = useFormStatus(); // 永远是 undefined!
return <form><button disabled={pending}>Submit</button></form>;
}
// ✅ 在子组件中调用
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>Submit</button>;
}
function Form() {
return <form><SubmitButton /></form>;
}
// === useOptimistic 错误 ===
// ❌ 用于关键业务操作
function PaymentButton() {
const [optimisticPaid, setPaid] = useOptimistic(false);
const handlePay = async () => {
setPaid(true); // 危险:显示已支付但可能失败
await processPayment();
};
}
// ❌ 没有处理回滚后的 UI 状态
const [optimisticLikes, addLike] = useOptimistic(likes);
// 失败后 UI 回滚,但用户可能困惑为什么点赞消失了
// ✅ 提供失败反馈
const handleLike = async () => {
addLike(1);
try {
await likePost();
} catch {
toast.error('点赞失败,请重试'); // 通知用户
}
};React 19 表单检查清单
- [ ] useActionState 返回新状态而不是 setState
- [ ] useActionState 正确使用 isPending 禁用提交
- [ ] useFormStatus 在 form 子组件中调用
- [ ] useOptimistic 不用于关键业务(支付、删除等)
- [ ] useOptimistic 失败时有用户反馈
- [ ] Server Action 正确标记 'use server'
Suspense 与流式渲染错误
// === Suspense 边界错误 ===
// ❌ 整个页面一个 Suspense——慢内容阻塞快内容
function BadPage() {
return (
<Suspense fallback={<FullPageLoader />}>
<FastHeader /> {/* 快 */}
<SlowMainContent /> {/* 慢——阻塞整个页面 */}
<FastFooter /> {/* 快 */}
</Suspense>
);
}
// ✅ 独立边界,互不阻塞
function GoodPage() {
return (
<>
<FastHeader />
<Suspense fallback={<ContentSkeleton />}>
<SlowMainContent />
</Suspense>
<FastFooter />
</>
);
}
// ❌ 没有 Error Boundary
function NoErrorHandling() {
return (
<Suspense fallback={<Loading />}>
<DataFetcher /> {/* 抛错导致白屏 */}
</Suspense>
);
}
// ✅ Error Boundary + Suspense
function WithErrorHandling() {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<Suspense fallback={<Loading />}>
<DataFetcher />
</Suspense>
</ErrorBoundary>
);
}
// === use() Hook 错误 ===
// ❌ 在组件外创建 Promise(每次渲染新 Promise)
function BadUse() {
const data = use(fetchData()); // 每次渲染都创建新 Promise!
return <div>{data}</div>;
}
// ✅ 在父组件创建,通过 props 传递
function Parent() {
const dataPromise = useMemo(() => fetchData(), []);
return <Child dataPromise={dataPromise} />;
}
function Child({ dataPromise }) {
const data = use(dataPromise);
return <div>{data}</div>;
}
// === Next.js Streaming 错误 ===
// ❌ 在 layout.tsx 中 await 慢数据——阻塞所有子页面
// app/layout.tsx
export default async function Layout({ children }) {
const config = await fetchSlowConfig(); // 阻塞整个应用!
return <ConfigProvider value={config}>{children}</ConfigProvider>;
}
// ✅ 将慢数据放在页面级别或使用 Suspense
// app/layout.tsx
export default function Layout({ children }) {
return (
<Suspense fallback={<ConfigSkeleton />}>
<ConfigProvider>{children}</ConfigProvider>
</Suspense>
);
}Suspense 检查清单
- [ ] 慢内容有独立的 Suspense 边界
- [ ] 每个 Suspense 有对应的 Error Boundary
- [ ] fallback 是有意义的骨架屏(不是简单 spinner)
- [ ] use() 的 Promise 不在渲染时创建
- [ ] 没有在 layout 中 await 慢数据
- [ ] 嵌套层级不超过 3 层
TanStack Query 错误
// === 查询配置错误 ===
// ❌ queryKey 不包含查询参数
function BadQuery({ userId, filters }) {
const { data } = useQuery({
queryKey: ['users'], // 缺少 userId 和 filters!
queryFn: () => fetchUsers(userId, filters),
});
// userId 或 filters 变化时数据不会更新
}
// ✅ queryKey 包含所有影响数据的参数
function GoodQuery({ userId, filters }) {
const { data } = useQuery({
queryKey: ['users', userId, filters],
queryFn: () => fetchUsers(userId, filters),
});
}
// ❌ staleTime: 0 导致过度请求
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
// 默认 staleTime: 0,每次组件挂载/窗口聚焦都会 refetch
});
// ✅ 设置合理的 staleTime
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
staleTime: 5 * 60 * 1000, // 5 分钟内不会自动 refetch
});
// === useSuspenseQuery 错误 ===
// ❌ useSuspenseQuery + enabled(不支持)
const { data } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // 错误!useSuspenseQuery 不支持 enabled
});
// ✅ 条件渲染实现
function UserQuery({ userId }) {
const { data } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
return <UserProfile user={data} />;
}
function Parent({ userId }) {
if (!userId) return <SelectUser />;
return (
<Suspense fallback={<UserSkeleton />}>
<UserQuery userId={userId} />
</Suspense>
);
}
// === Mutation 错误 ===
// ❌ Mutation 成功后不 invalidate 查询
const mutation = useMutation({
mutationFn: updateUser,
// 忘记 invalidate,UI 显示旧数据
});
// ✅ 成功后 invalidate 相关查询
const mutation = useMutation({
mutationFn: updateUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
// ❌ 乐观更新不处理回滚
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
// 没有保存旧数据,失败后无法回滚!
},
});
// ✅ 完整的乐观更新
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previous = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
return { previous };
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
// === v5 迁移错误 ===
// ❌ 使用废弃的 API
const { data, isLoading } = useQuery(['key'], fetchFn); // v4 语法
// ✅ v5 单一对象参数
const { data, isPending } = useQuery({
queryKey: ['key'],
queryFn: fetchFn,
});
// ❌ 混淆 isPending 和 isLoading
if (isLoading) return <Spinner />;
// v5 中 isLoading = isPending && isFetching
// ✅ 根据意图选择
if (isPending) return <Spinner />; // 没有缓存数据
// 或
if (isFetching) return <Refreshing />; // 正在后台刷新TanStack Query Checklist
- [ ] queryKey 包含所有影响数据的参数
- [ ] 设置了合理的 staleTime(不是默认 0)
- [ ] useSuspenseQuery 不使用 enabled
- [ ] Mutation 成功后 invalidate 相关查询
- [ ] 乐观更新有完整的回滚逻辑
- [ ] v5 使用单一对象参数语法
- [ ] 理解 isPending vs isLoading vs isFetching
TypeScript/JavaScript 常见错误
- [ ]
==instead of=== - [ ] 迭代时修改数组/对象
- [ ]
thiscontext lost in callbacks - [ ] 列表缺少
key属性 - [ ] Closure capturing loop variable
- [ ] parseInt without radix parameter
Vue 3
响应性丢失
<!-- ❌ 解构 reactive 丢失响应性 -->
<script setup>
const state = reactive({ count: 0 })
const { count } = state // count 不是响应式的!
</script>
<!-- ✅ 使用 toRefs -->
<script setup>
const state = reactive({ count: 0 })
const { count } = toRefs(state) // count.value 是响应式的
</script>Props 响应性传递
<!-- ❌ 传递 props 值到 composable 丢失响应性 -->
<script setup>
const props = defineProps<{ id: string }>()
const { data } = useFetch(props.id) // id 变化时不会重新获取!
</script>
<!-- ✅ 使用 toRef 或 getter -->
<script setup>
const props = defineProps<{ id: string }>()
const { data } = useFetch(() => props.id) // getter 保持响应性
// 或
const { data } = useFetch(toRef(props, 'id'))
</script>Watch 清理
<!-- ❌ 异步 watch 无清理,导致竞态 -->
<script setup>
watch(id, async (newId) => {
const data = await fetchData(newId)
result.value = data // 旧请求可能覆盖新结果!
})
</script>
<!-- ✅ 使用 onCleanup 取消旧请求 -->
<script setup>
watch(id, async (newId, _, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())
const data = await fetchData(newId, controller.signal)
result.value = data
})
</script>Computed 副作用
<!-- ❌ computed 中修改其他状态 -->
<script setup>
const total = computed(() => {
sideEffect.value++ // 副作用!每次访问都会执行
return items.value.reduce((a, b) => a + b, 0)
})
</script>
<!-- ✅ computed 只做纯计算 -->
<script setup>
const total = computed(() => {
return items.value.reduce((a, b) => a + b, 0)
})
// 副作用放 watch
watch(total, () => { sideEffect.value++ })
</script>模板常见错误
<!-- ❌ v-if 和 v-for 同时使用(v-if 优先级更高) -->
<template>
<div v-for="item in items" v-if="item.visible" :key="item.id">
{{ item.name }}
</div>
</template>
<!-- ✅ 使用 computed 或 template 包裹 -->
<template>
<template v-for="item in items" :key="item.id">
<div v-if="item.visible">{{ item.name }}</div>
</template>
</template>Vue 常见错误
- [ ] props 传递给 composable 时未保持响应性
- [ ] watch 异步回调无清理函数
- [ ] computed 中产生副作用
- [ ] v-for 使用 index 作为 key(列表会重排时)
- [ ] v-if 和 v-for 在同一元素上
- [ ] defineProps 未使用 TypeScript 类型声明
- [ ] withDefaults 对象默认值未使用工厂函数
- [ ] 直接修改 props(而不是 emit)
- [ ] watchEffect 依赖不明确导致过度触发
Python
可变默认参数
# ❌ Bug: List shared across all calls
def add_item(item, items=[]):
items.append(item)
return items
# ✅ Correct
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return itemsException Handling
# ❌ Catching everything, including KeyboardInterrupt
try:
risky_operation()
except:
pass
# ✅ Catch specific exceptions
try:
risky_operation()
except ValueError as e:
logger.error(f"Invalid value: {e}")
raiseClass Attributes
# ❌ Shared mutable class attribute
class User:
permissions = [] # Shared across all instances!
# ✅ Initialize in __init__
class User:
def __init__(self):
self.permissions = []Python 常见错误
- [ ] 方法中遗漏
self参数 - [ ] 迭代时修改列表
- [ ] 在循环中做字符串拼接(应使用 join)
- [ ] 文件未关闭(应使用
with语句)
Rust
所有权与借用
// ❌ Use after move
let s = String::from("hello");
let s2 = s;
println!("{}", s); // Error: s was moved
// ✅ Clone if needed (but consider if clone is necessary)
let s = String::from("hello");
let s2 = s.clone();
println!("{}", s); // OK
// ❌ 用 clone() 绕过借用检查器(反模式)
fn process(data: &Data) {
let owned = data.clone(); // 不必要的 clone
do_something(owned);
}
// ✅ 正确使用借用
fn process(data: &Data) {
do_something(data); // 传递引用
}
// ❌ 在结构体中存储借用(通常是坏主意)
struct Parser<'a> {
input: &'a str, // 生命周期复杂化
position: usize,
}
// ✅ 使用拥有的数据
struct Parser {
input: String, // 拥有数据,简化生命周期
position: usize,
}
// ❌ 迭代时修改集合
let mut vec = vec![1, 2, 3];
for item in &vec {
vec.push(*item); // Error: cannot borrow as mutable
}
// ✅ 收集到新集合
let vec = vec![1, 2, 3];
let new_vec: Vec<_> = vec.iter().map(|x| x * 2).collect();Unsafe 代码审查
// ❌ unsafe 没有安全注释
unsafe {
ptr::write(dest, value);
}
// ✅ 必须有 SAFETY 注释说明不变量
// SAFETY: dest 指针由 Vec::as_mut_ptr() 获得,保证:
// 1. 指针有效且已对齐
// 2. 目标内存未被其他引用借用
// 3. 写入不会超出分配的容量
unsafe {
ptr::write(dest, value);
}
// ❌ unsafe fn 没有 # Safety 文档
pub unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self { ... }
// ✅ 必须文档化安全契约
/// Creates a new instance from raw parts.
///
/// # Safety
///
/// - `ptr` must have been allocated via `GlobalAlloc`
/// - `len` must be less than or equal to the allocated capacity
/// - The caller must ensure no other references to the memory exist
pub unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self { ... }
// ❌ 跨模块 unsafe 不变量
mod a {
pub fn set_flag() { FLAG = true; } // 安全代码影响 unsafe
}
mod b {
pub unsafe fn do_thing() {
if FLAG { /* assumes FLAG means something */ }
}
}
// ✅ 将 unsafe 边界封装在单一模块
mod safe_wrapper {
// 所有 unsafe 逻辑在一个模块内
// 对外提供 safe API
}异步/并发
// ❌ 在异步上下文中阻塞
async fn bad_fetch(url: &str) -> Result<String> {
let resp = reqwest::blocking::get(url)?; // 阻塞整个运行时!
Ok(resp.text()?)
}
// ✅ 使用异步版本
async fn good_fetch(url: &str) -> Result<String> {
let resp = reqwest::get(url).await?;
Ok(resp.text().await?)
}
// ❌ 跨 .await 持有 Mutex
async fn bad_lock(mutex: &Mutex<Data>) {
let guard = mutex.lock().unwrap();
some_async_op().await; // 持锁跨越 await!
drop(guard);
}
// ✅ 缩短锁持有时间
async fn good_lock(mutex: &Mutex<Data>) {
let data = {
let guard = mutex.lock().unwrap();
guard.clone() // 获取数据后立即释放锁
};
some_async_op().await;
// 处理 data
}
// ❌ 在异步函数中使用 std::sync::Mutex
async fn bad_async_mutex(mutex: &std::sync::Mutex<Data>) {
let _guard = mutex.lock().unwrap(); // 可能死锁
tokio::time::sleep(Duration::from_secs(1)).await;
}
// ✅ 使用 tokio::sync::Mutex(如果必须跨 await)
async fn good_async_mutex(mutex: &tokio::sync::Mutex<Data>) {
let _guard = mutex.lock().await;
tokio::time::sleep(Duration::from_secs(1)).await;
}
// ❌ 忘记 Future 是惰性的
fn bad_spawn() {
let future = async_operation(); // 没有执行!
// future 被丢弃,什么都没发生
}
// ✅ 必须 await 或 spawn
async fn good_spawn() {
async_operation().await; // 执行
// 或
tokio::spawn(async_operation()); // 后台执行
}
// ❌ spawn 任务缺少 'static
async fn bad_spawn_lifetime(data: &str) {
tokio::spawn(async {
println!("{}", data); // Error: data 不是 'static
});
}
// ✅ 使用 move 或 Arc
async fn good_spawn_lifetime(data: String) {
tokio::spawn(async move {
println!("{}", data); // OK: 拥有数据
});
}错误处理
// ❌ 生产代码中使用 unwrap/expect
fn bad_parse(input: &str) -> i32 {
input.parse().unwrap() // panic!
}
// ✅ 正确传播错误
fn good_parse(input: &str) -> Result<i32, ParseIntError> {
input.parse()
}
// ❌ 吞掉错误信息
fn bad_error_handling() -> Result<()> {
match operation() {
Ok(v) => Ok(v),
Err(_) => Err(anyhow!("operation failed")) // 丢失原始错误
}
}
// ✅ 使用 context 添加上下文
fn good_error_handling() -> Result<()> {
operation().context("failed to perform operation")?;
Ok(())
}
// ❌ 库代码使用 anyhow(应该用 thiserror)
// lib.rs
pub fn parse_config(path: &str) -> anyhow::Result<Config> {
// 调用者无法区分错误类型
}
// ✅ 库代码用 thiserror 定义错误类型
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("failed to read config file: {0}")]
Io(#[from] std::io::Error),
#[error("invalid config format: {0}")]
Parse(#[from] serde_json::Error),
}
pub fn parse_config(path: &str) -> Result<Config, ConfigError> {
// 调用者可以 match 不同错误
}
// ❌ 忽略 must_use 返回值
fn bad_ignore_result() {
some_fallible_operation(); // 警告:unused Result
}
// ✅ 显式处理或标记忽略
fn good_handle_result() {
let _ = some_fallible_operation(); // 显式忽略
// 或
some_fallible_operation().ok(); // 转换为 Option
}性能陷阱
// ❌ 不必要的 collect
fn bad_process(items: &[i32]) -> i32 {
items.iter()
.filter(|x| **x > 0)
.collect::<Vec<_>>() // 不必要的分配
.iter()
.sum()
}
// ✅ 惰性迭代
fn good_process(items: &[i32]) -> i32 {
items.iter()
.filter(|x| **x > 0)
.sum()
}
// ❌ 循环中重复分配
fn bad_loop() -> String {
let mut result = String::new();
for i in 0..1000 {
result = result + &i.to_string(); // 每次迭代都重新分配!
}
result
}
// ✅ 预分配或使用 push_str
fn good_loop() -> String {
let mut result = String::with_capacity(4000); // 预分配
for i in 0..1000 {
write!(result, "{}", i).unwrap(); // 原地追加
}
result
}
// ❌ 过度使用 clone
fn bad_clone(data: &HashMap<String, Vec<u8>>) -> Vec<u8> {
data.get("key").cloned().unwrap_or_default()
}
// ✅ 返回引用或使用 Cow
fn good_ref(data: &HashMap<String, Vec<u8>>) -> &[u8] {
data.get("key").map(|v| v.as_slice()).unwrap_or(&[])
}
// ❌ 大结构体按值传递
fn bad_pass(data: LargeStruct) { ... } // 拷贝整个结构体
// ✅ 传递引用
fn good_pass(data: &LargeStruct) { ... }
// ❌ Box<dyn Trait> 用于小型已知类型
fn bad_trait_object() -> Box<dyn Iterator<Item = i32>> {
Box::new(vec![1, 2, 3].into_iter())
}
// ✅ 使用 impl Trait
fn good_impl_trait() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter()
}
// ❌ retain 比 filter+collect 慢(某些场景)
vec.retain(|x| x.is_valid()); // O(n) 但常数因子大
// ✅ 如果不需要原地修改,考虑 filter
let vec: Vec<_> = vec.into_iter().filter(|x| x.is_valid()).collect();生命周期与引用
// ❌ 返回局部变量的引用
fn bad_return_ref() -> &str {
let s = String::from("hello");
&s // Error: s will be dropped
}
// ✅ 返回拥有的数据或静态引用
fn good_return_owned() -> String {
String::from("hello")
}
// ❌ 生命周期过度泛化
fn bad_lifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // 'b 没有被使用
}
// ✅ 简化生命周期
fn good_lifetime(x: &str, _y: &str) -> &str {
x // 编译器自动推断
}
// ❌ 结构体持有多个相关引用但生命周期独立
struct Bad<'a, 'b> {
name: &'a str,
data: &'b [u8], // 通常应该是同一个生命周期
}
// ✅ 相关数据使用相同生命周期
struct Good<'a> {
name: &'a str,
data: &'a [u8],
}Rust 审查清单
所有权与借用
- [ ] clone() 是有意为之,不是绕过借用检查器
- [ ] 避免在结构体中存储借用(除非必要)
- [ ] Rc/Arc 使用合理,没有隐藏不必要的共享状态
- [ ] 没有不必要的 RefCell(运行时检查 vs 编译时)
Unsafe 代码
- [ ] 每个 unsafe 块有 SAFETY 注释
- [ ] unsafe fn 有 # Safety 文档
- [ ] 安全不变量被清晰记录
- [ ] unsafe 边界尽可能小
异步/并发
- [ ] 没有在异步上下文中阻塞
- [ ] 没有跨 .await 持有 std::sync 锁
- [ ] spawn 的任务满足 'static 约束
- [ ] Future 被正确 await 或 spawn
- [ ] 锁的顺序一致(避免死锁)
错误处理
- [ ] 库代码使用 thiserror,应用代码使用 anyhow
- [ ] 错误有足够的上下文信息
- [ ] 没有在生产代码中 unwrap/expect
- [ ] must_use 返回值被正确处理
性能
- [ ] 避免不必要的 collect()
- [ ] 大数据结构传引用
- [ ] 字符串拼接使用 String::with_capacity 或 write!
- [ ] impl Trait 优于 Box<dyn Trait>(当可能时)
类型系统
- [ ] 善用 newtype 模式增加类型安全
- [ ] 枚举穷尽匹配(没有 _ 通配符隐藏新变体)
- [ ] 生命周期尽可能简化
SQL
注入漏洞
-- ❌ String concatenation (SQL injection risk)
query = "SELECT * FROM users WHERE id = " + user_id
-- ✅ Parameterized queries
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, (user_id,))性能问题
- [ ] 过滤/关联列缺少索引
- [ ] 使用了
SELECT *而非指定列 - [ ] 存在 N+1 查询模式
- [ ] 大表查询缺少 LIMIT
- [ ] 子查询与 JOIN 选择低效
常见错误
- [ ] 错误处理 NULL 比较
- [ ] 相关操作缺少事务包裹
- [ ] JOIN 类型使用不正确
- [ ] 大小写敏感性问题
- [ ] 日期/时区处理错误
API 设计
REST 常见问题
- [ ] 资源命名不一致
- [ ] HTTP 方法使用错误(如幂等操作用了 POST)
- [ ] 列表接口缺少分页
- [ ] 状态码使用不正确
- [ ] 缺少限流
数据校验
- [ ] 缺少输入校验
- [ ] 数据类型校验不正确
- [ ] 缺少长度/范围校验
- [ ] 未对用户输入做清洗
- [ ] 仅依赖客户端校验
测试
测试质量问题
- [ ] 测试实现细节而非行为
- [ ] 缺少边界场景测试
- [ ] 测试不稳定(非确定性)
- [ ] 测试依赖外部系统
- [ ] 缺少负向测试(错误场景)
- [ ] 测试初始化过于复杂
C++ 代码审查指南
面向 C++ 的代码审查指南,重点关注内存安全、生命周期、API 设计与性能。示例默认基于 C++17/20。
目录
---
所有权与 RAII
优先使用 RAII 与智能指针
用 RAII 表达所有权。默认使用 std::unique_ptr;只有在确实需要共享生命周期时才使用 std::shared_ptr。
// ❌ Bad: 手工 new/delete,且有早返回
Foo* make_foo() {
Foo* foo = new Foo();
if (!foo->Init()) {
delete foo;
return nullptr;
}
return foo;
}
// ✅ Good: 用 unique_ptr 表达 RAII
std::unique_ptr<Foo> make_foo() {
auto foo = std::make_unique<Foo>();
if (!foo->Init()) {
return {};
}
return foo;
}包装 C 资源
// ✅ Good: 用 unique_ptr 包装 FILE*
using FilePtr = std::unique_ptr<FILE, decltype(&fclose)>;
FilePtr open_file(const char* path) {
return FilePtr(fopen(path, "rb"), &fclose);
}---
生命周期与引用
避免悬垂引用与悬空视图
std::string_view 和 std::span 不拥有数据,必须保证数据所有者生命周期长于视图。
// ❌ Bad: 返回指向临时对象的 string_view
std::string_view bad_view() {
std::string s = make_name();
return s; // dangling
}
// ✅ Good: 返回拥有所有权的 string
std::string good_name() {
return make_name();
}
// ✅ Good: 视图绑定调用方持有的数据
std::string_view good_view(const std::string& s) {
return s;
}Lambda 捕获
// ❌ Bad: 捕获会逃逸的引用
std::function<void()> make_task() {
int value = 42;
return [&]() { use(value); }; // dangling
}
// ✅ Good: 按值捕获
std::function<void()> make_task() {
int value = 42;
return [value]() { use(value); };
}---
拷贝与移动语义
Rule of 0/3/5
优先用 RAII 类型遵循 Rule of 0。若类型自己持有资源,则必须显式定义或禁用拷贝/移动操作。
// ❌ Bad: 原始资源所有权 + 默认拷贝
struct Buffer {
int* data;
size_t size;
explicit Buffer(size_t n) : data(new int[n]), size(n) {}
~Buffer() { delete[] data; }
// copy ctor/assign 隐式生成 -> double delete
};
// ✅ Good: 用 std::vector 实现 Rule of 0
struct Buffer {
std::vector<int> data;
explicit Buffer(size_t n) : data(n) {}
};禁止不需要的拷贝
struct Socket {
Socket() = default;
~Socket() { close(); }
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
Socket(Socket&&) noexcept = default;
Socket& operator=(Socket&&) noexcept = default;
};---
const-correctness 与 API 设计
使用 const 与 explicit
class User {
public:
const std::string& name() const { return name_; }
void set_name(std::string name) { name_ = std::move(name); }
private:
std::string name_;
};
struct Millis {
explicit Millis(int v) : value(v) {}
int value;
};避免对象切片
struct Shape { virtual ~Shape() = default; };
struct Circle : Shape { void draw() const; };
// ❌ Bad: Circle 被切片成 Shape
void draw(Shape shape);
// ✅ Good: 通过引用传递
void draw(const Shape& shape);使用 override 和 final
struct Base {
virtual void run() = 0;
};
struct Worker final : Base {
void run() override {}
};---
错误处理与异常安全
用 RAII 兜底清理
// ✅ Good: 异常发生时 RAII 自动清理
void process() {
std::vector<int> data = load_data(); // safe cleanup
do_work(data);
}析构函数不要抛异常
struct File {
~File() noexcept { close(); }
void close();
};正常失败优先返回期望值类型
// ✅ 预期失败:使用 optional 或 expected
std::optional<int> parse_int(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
}---
并发
保护共享数据
// ❌ Bad: 数据竞争
int counter = 0;
void inc() { counter++; }
// ✅ Good: 原子操作
std::atomic<int> counter{0};
void inc() { counter.fetch_add(1, std::memory_order_relaxed); }使用 RAII 锁
std::mutex mu;
std::vector<int> data;
void add(int v) {
std::lock_guard<std::mutex> lock(mu);
data.push_back(v);
}---
性能与分配
避免重复分配
// ❌ Bad: 多次触发重分配
std::vector<int> build(int n) {
std::vector<int> out;
for (int i = 0; i < n; ++i) {
out.push_back(i);
}
return out;
}
// ✅ Good: 预留容量
std::vector<int> build(int n) {
std::vector<int> out;
out.reserve(static_cast<size_t>(n));
for (int i = 0; i < n; ++i) {
out.push_back(i);
}
return out;
}字符串拼接
// ❌ Bad: 反复分配
std::string join(const std::vector<std::string>& parts) {
std::string out;
for (const auto& p : parts) {
out += p;
}
return out;
}
// ✅ Good: 先计算总长度并 reserve
std::string join(const std::vector<std::string>& parts) {
size_t total = 0;
for (const auto& p : parts) {
total += p.size();
}
std::string out;
out.reserve(total);
for (const auto& p : parts) {
out += p;
}
return out;
}---
模板与类型安全
优先使用受约束模板(C++20)
// ❌ Bad: 泛型过宽
template <typename T>
T add(T a, T b) {
return a + b;
}
// ✅ Good: 添加约束
template <typename T>
requires std::is_integral_v<T>
T add(T a, T b) {
return a + b;
}使用 static_assert 约束不变量
template <typename T>
struct Packet {
static_assert(std::is_trivially_copyable_v<T>,
"Packet payload must be trivially copyable");
T payload;
};---
工具链与构建检查
# 警告
clang++ -Wall -Wextra -Werror -Wconversion -Wshadow -std=c++20 ...
# Sanitizer(调试构建)
clang++ -fsanitize=address,undefined -fno-omit-frame-pointer -g ...
clang++ -fsanitize=thread -fno-omit-frame-pointer -g ...
# 静态分析
clang-tidy src/*.cpp -- -std=c++20
# 格式化
clang-format -i src/*.cpp include/*.h---
审查检查清单
安全与生命周期
- [ ] 所有权表达明确(RAII,默认 unique_ptr)
- [ ] 不存在悬垂引用或悬空视图
- [ ] 资源持有类型遵循 Rule of 0/3/5
- [ ] 业务代码中没有裸
new/delete - [ ] 析构函数是
noexcept且不会抛异常
API 与设计
- [ ] const-correctness 应用一致
- [ ] 需要的构造函数均加
explicit - [ ] 虚函数使用 override/final
- [ ] 不存在对象切片(按引用或指针传递)
并发
- [ ] 共享数据有保护(mutex 或 atomics)
- [ ] 加锁顺序一致
- [ ] 持锁期间不做阻塞操作
性能
- [ ] 避免不必要的分配(reserve、move)
- [ ] 热路径避免不必要拷贝
- [ ] 算法复杂度合理
工具与测试
- [ ] 开启警告后可干净构建
- [ ] 关键路径已运行 Sanitizer
- [ ] 静态分析(clang-tidy)结果已处理
CSS / Less / Sass 审查指南
CSS 及预处理器代码审查指南,覆盖性能、可维护性、响应式设计和浏览器兼容性。
CSS 变量与硬编码
应该使用变量的场景
/* ❌ 硬编码 - 难以维护 */
.button {
background: #3b82f6;
border-radius: 8px;
}
.card {
border: 1px solid #3b82f6;
border-radius: 8px;
}
/* ✅ 使用 CSS 变量 */
:root {
--color-primary: #3b82f6;
--radius-md: 8px;
}
.button {
background: var(--color-primary);
border-radius: var(--radius-md);
}
.card {
border: 1px solid var(--color-primary);
border-radius: var(--radius-md);
}变量命名规范
/* 推荐的变量分类 */
:root {
/* 颜色 */
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--color-text: #1f2937;
--color-text-muted: #6b7280;
--color-bg: #ffffff;
--color-border: #e5e7eb;
/* 间距 */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
/* 字体 */
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
--font-weight-normal: 400;
--font-weight-bold: 700;
/* 圆角 */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* 阴影 */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
/* 过渡 */
--transition-fast: 150ms ease;
--transition-normal: 300ms ease;
}变量作用域建议
/* ✅ 组件级变量 - 减少全局污染 */
.card {
--card-padding: var(--spacing-md);
--card-radius: var(--radius-md);
padding: var(--card-padding);
border-radius: var(--card-radius);
}
/* ⚠️ 避免频繁用 JS 动态修改变量 - 影响性能 */审查清单
- [ ] 颜色值是否使用变量?
- [ ] 间距是否来自设计系统?
- [ ] 重复值是否提取为变量?
- [ ] 变量命名是否语义化?
---
!important 使用规范
何时可以使用
/* ✅ 工具类 - 明确需要覆盖 */
.hidden { display: none !important; }
.sr-only { position: absolute !important; }
/* ✅ 覆盖第三方库样式(无法修改源码时) */
.third-party-modal {
z-index: 9999 !important;
}
/* ✅ 打印样式 */
@media print {
.no-print { display: none !important; }
}何时禁止使用
/* ❌ 解决特异性问题 - 应该重构选择器 */
.button {
background: blue !important; /* 为什么需要 !important? */
}
/* ❌ 覆盖自己写的样式 */
.card { padding: 20px; }
.card { padding: 30px !important; } /* 直接修改原规则 */
/* ❌ 在组件样式中 */
.my-component .title {
font-size: 24px !important; /* 破坏组件封装 */
}替代方案
/* 问题:需要覆盖 .btn 的样式 */
/* ❌ 使用 !important */
.my-btn {
background: red !important;
}
/* ✅ 提高特异性 */
button.my-btn {
background: red;
}
/* ✅ 使用更具体的选择器 */
.container .my-btn {
background: red;
}
/* ✅ 使用 :where() 降低被覆盖样式的特异性 */
:where(.btn) {
background: blue; /* 特异性为 0 */
}
.my-btn {
background: red; /* 可以正常覆盖 */
}审查问题
🔴 [blocking] "发现 15 处 !important,请说明每处的必要性"
🟡 [important] "这个 !important 可以通过调整选择器特异性来解决"
💡 [suggestion] "考虑使用 CSS Layers (@layer) 来管理样式优先级"---
性能考虑
🔴 高危性能问题
1. transition: all 问题
/* ❌ 性能杀手 - 浏览器检查所有可动画属性 */
.button {
transition: all 0.3s ease;
}
/* ✅ 明确指定属性 */
.button {
transition: background-color 0.3s ease, transform 0.3s ease;
}
/* ✅ 多属性时使用变量 */
.button {
--transition-duration: 0.3s;
transition:
background-color var(--transition-duration) ease,
box-shadow var(--transition-duration) ease,
transform var(--transition-duration) ease;
}2. box-shadow 动画
/* ❌ 每帧触发重绘 - 严重影响性能 */
.card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: box-shadow 0.3s ease;
}
.card:hover {
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
/* ✅ 使用伪元素 + opacity */
.card {
position: relative;
}
.card::after {
content: '';
position: absolute;
inset: 0;
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
border-radius: inherit;
}
.card:hover::after {
opacity: 1;
}3. 触发布局(Reflow)的属性
/* ❌ 动画这些属性会触发布局重计算 */
.bad-animation {
transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s, margin 0.3s;
}
/* ✅ 只动画 transform 和 opacity(仅触发合成) */
.good-animation {
transition: transform 0.3s, opacity 0.3s;
}
/* 位移用 translate 代替 top/left */
.move {
transform: translateX(100px); /* ✅ */
/* left: 100px; */ /* ❌ */
}
/* 缩放用 scale 代替 width/height */
.grow {
transform: scale(1.1); /* ✅ */
/* width: 110%; */ /* ❌ */
}🟡 中等性能问题
复杂选择器
/* ❌ 过深的嵌套 - 选择器匹配慢 */
.page .container .content .article .section .paragraph span {
color: red;
}
/* ✅ 扁平化 */
.article-text {
color: red;
}
/* ❌ 通配符选择器 */
* { box-sizing: border-box; } /* 影响所有元素 */
[class*="icon-"] { display: inline; } /* 属性选择器较慢 */
/* ✅ 限制范围 */
.icon-box * { box-sizing: border-box; }大量阴影和滤镜
/* ⚠️ 复杂阴影影响渲染性能 */
.heavy-shadow {
box-shadow:
0 1px 2px rgba(0,0,0,0.1),
0 2px 4px rgba(0,0,0,0.1),
0 4px 8px rgba(0,0,0,0.1),
0 8px 16px rgba(0,0,0,0.1),
0 16px 32px rgba(0,0,0,0.1); /* 5 层阴影 */
}
/* ⚠️ 滤镜消耗 GPU */
.blur-heavy {
filter: blur(20px) brightness(1.2) contrast(1.1);
backdrop-filter: blur(10px); /* 更消耗性能 */
}性能优化建议
/* 使用 will-change 提示浏览器(谨慎使用) */
.animated-element {
will-change: transform, opacity;
}
/* 动画完成后移除 will-change */
.animated-element.idle {
will-change: auto;
}
/* 使用 contain 限制重绘范围 */
.card {
contain: layout paint; /* 告诉浏览器内部变化不影响外部 */
}审查清单
- [ ] 是否使用
transition: all? - [ ] 是否动画 width/height/top/left?
- [ ] box-shadow 是否被动画?
- [ ] 选择器嵌套是否超过 3 层?
- [ ] 是否有不必要的
will-change?
---
响应式设计检查点
移动端优先(Mobile First)原则
/* ✅ Mobile First - 基础样式针对移动端 */
.container {
padding: 16px;
display: flex;
flex-direction: column;
}
/* 逐步增强 */
@media (min-width: 768px) {
.container {
padding: 24px;
flex-direction: row;
}
}
@media (min-width: 1024px) {
.container {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
}
/* ❌ Desktop First - 需要覆盖更多样式 */
.container {
max-width: 1200px;
padding: 32px;
flex-direction: row;
}
@media (max-width: 1023px) {
.container {
padding: 24px;
}
}
@media (max-width: 767px) {
.container {
padding: 16px;
flex-direction: column;
max-width: none;
}
}断点建议
/* 推荐断点(基于内容而非设备) */
:root {
--breakpoint-sm: 640px; /* 大手机 */
--breakpoint-md: 768px; /* 平板竖屏 */
--breakpoint-lg: 1024px; /* 平板横屏/小笔记本 */
--breakpoint-xl: 1280px; /* 桌面 */
--breakpoint-2xl: 1536px; /* 大桌面 */
}
/* 使用示例 */
@media (min-width: 768px) { /* md */ }
@media (min-width: 1024px) { /* lg */ }响应式审查清单
- [ ] 是否采用 Mobile First?
- [ ] 断点是否基于内容断裂点而非设备?
- [ ] 是否避免断点重叠?
- [ ] 文字是否使用相对单位(rem/em)?
- [ ] 触摸目标是否足够大(≥44px)?
- [ ] 是否测试了横竖屏切换?
常见问题
/* ❌ 固定宽度 */
.container {
width: 1200px;
}
/* ✅ 最大宽度 + 弹性 */
.container {
width: 100%;
max-width: 1200px;
padding-inline: 16px;
}
/* ❌ 固定高度的文本容器 */
.text-box {
height: 100px; /* 文字可能溢出 */
}
/* ✅ 最小高度 */
.text-box {
min-height: 100px;
}
/* ❌ 小触摸目标 */
.small-button {
padding: 4px 8px; /* 太小,难以点击 */
}
/* ✅ 足够的触摸区域 */
.touch-button {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
}---
浏览器兼容性
需要检查的特性
| 特性 | 兼容性 | 建议 |
|---|---|---|
| CSS Grid | 现代浏览器 ✅ | IE 需要 Autoprefixer + 测试 |
| Flexbox | 广泛支持 ✅ | 旧版需要前缀 |
| CSS Variables | 现代浏览器 ✅ | IE 不支持,需要回退 |
gap (flexbox) | 较新 ⚠️ | Safari 14.1+ |
:has() | 较新 ⚠️ | Firefox 121+ |
container queries | 较新 ⚠️ | 2023 年后的浏览器 |
@layer | 较新 ⚠️ | 检查目标浏览器 |
回退策略
/* CSS 变量回退 */
.button {
background: #3b82f6; /* 回退值 */
background: var(--color-primary); /* 现代浏览器 */
}
/* Flexbox gap 回退 */
.flex-container {
display: flex;
gap: 16px;
}
/* 旧浏览器回退 */
.flex-container > * + * {
margin-left: 16px;
}
/* Grid 回退 */
.grid {
display: flex;
flex-wrap: wrap;
}
@supports (display: grid) {
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
}Autoprefixer 配置
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')({
// 根据 browserslist 配置
grid: 'autoplace', // 启用 Grid 前缀(IE 支持)
flexbox: 'no-2009', // 只用现代 flexbox 语法
}),
],
};
// package.json
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11" // 根据项目需求
]
}审查清单
- [ ] 是否检查了 Can I Use?
- [ ] 新特性是否有回退方案?
- [ ] 是否配置了 Autoprefixer?
- [ ] browserslist 是否符合项目要求?
- [ ] 是否在目标浏览器中测试?
---
Less / Sass 特定问题
嵌套深度
/* ❌ 过深嵌套 - 编译后选择器过长 */
.page {
.container {
.content {
.article {
.title {
color: red; // 编译为 .page .container .content .article .title
}
}
}
}
}
/* ✅ 最多 3 层 */
.article {
&__title {
color: red;
}
&__content {
p { margin-bottom: 1em; }
}
}Mixin vs Extend vs 变量
/* 变量 - 用于单个值 */
$primary-color: #3b82f6;
/* Mixin - 用于可配置的代码块 */
@mixin button-variant($bg, $text) {
background: $bg;
color: $text;
&:hover {
background: darken($bg, 10%);
}
}
/* Extend - 用于共享相同样式(谨慎使用) */
%visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.sr-only {
@extend %visually-hidden;
}
/* ⚠️ @extend 的问题 */
// 可能产生意外的选择器组合
// 不能在 @media 中使用
// 优先使用 mixin审查清单
- [ ] 嵌套是否超过 3 层?
- [ ] 是否滥用 @extend?
- [ ] Mixin 是否过于复杂?
- [ ] 编译后的 CSS 大小是否合理?
---
快速审查清单
🔴 必须修复
□ transition: all
□ 动画 width/height/top/left/margin
□ 大量 !important
□ 硬编码的颜色/间距重复 >3 次
□ 选择器嵌套 >4 层🟡 建议修复
□ 缺少响应式处理
□ 使用 Desktop First
□ 复杂 box-shadow 被动画
□ 缺少浏览器兼容回退
□ CSS 变量作用域过大🟢 优化建议
□ 可以使用 CSS Grid 简化布局
□ 可以使用 CSS 变量提取重复值
□ 可以使用 @layer 管理优先级
□ 可以添加 contain 优化性能---
工具推荐
| 工具 | 用途 |
|---|---|
| Stylelint | CSS 代码检查 |
| PurgeCSS | 移除未使用 CSS |
| Autoprefixer | 自动添加前缀 |
| CSS Stats | 分析 CSS 统计 |
| Can I Use | 浏览器兼容性查询 |
---
参考资源
Go 代码审查指南
基于 Go 官方指南、Effective Go 和社区最佳实践的代码审查清单。
快速审查清单
必查项
- [ ] 错误是否正确处理(不忽略、有上下文)
- [ ] goroutine 是否有退出机制(避免泄漏)
- [ ] context 是否正确传递和取消
- [ ] 接收器类型选择是否合理(值/指针)
- [ ] 是否使用
gofmt格式化代码
高频问题
- [ ] 循环变量捕获问题(Go < 1.22)
- [ ] nil 检查是否完整
- [ ] map 是否初始化后使用
- [ ] defer 在循环中的使用
- [ ] 变量遮蔽(shadowing)
---
1. 错误处理
1.1 永远不要忽略错误
// ❌ 错误:忽略错误
result, _ := SomeFunction()
// ✅ 正确:处理错误
result, err := SomeFunction()
if err != nil {
return fmt.Errorf("some function failed: %w", err)
}1.2 错误包装与上下文
// ❌ 错误:丢失上下文
if err != nil {
return err
}
// ❌ 错误:使用 %v 丢失错误链
if err != nil {
return fmt.Errorf("failed: %v", err)
}
// ✅ 正确:使用 %w 保留错误链
if err != nil {
return fmt.Errorf("failed to process user %d: %w", userID, err)
}1.3 使用 errors.Is 和 errors.As
// ❌ 错误:直接比较(无法处理包装错误)
if err == sql.ErrNoRows {
// ...
}
// ✅ 正确:使用 errors.Is(支持错误链)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
// ✅ 正确:使用 errors.As 提取特定类型
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Printf("path error: %s", pathErr.Path)
}1.4 自定义错误类型
// ✅ 推荐:定义 sentinel 错误
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
)
// ✅ 推荐:带上下文的自定义错误
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}1.5 错误处理只做一次
// ❌ 错误:既记录又返回(重复处理)
if err != nil {
log.Printf("error: %v", err)
return err
}
// ✅ 正确:只返回,让调用者决定
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// ✅ 或者:只记录并处理(不返回)
if err != nil {
log.Printf("non-critical error: %v", err)
// 继续执行备用逻辑
}---
2. 并发与 Goroutine
2.1 避免 Goroutine 泄漏
// ❌ 错误:goroutine 永远无法退出
func bad() {
ch := make(chan int)
go func() {
val := <-ch // 永远阻塞,无人发送
fmt.Println(val)
}()
// 函数返回,goroutine 泄漏
}
// ✅ 正确:使用 context 或 done channel
func good(ctx context.Context) {
ch := make(chan int)
go func() {
select {
case val := <-ch:
fmt.Println(val)
case <-ctx.Done():
return // 优雅退出
}
}()
}2.2 Channel 使用规范
// ❌ 错误:向 nil channel 发送(永久阻塞)
var ch chan int
ch <- 1 // 永久阻塞
// ❌ 错误:向已关闭的 channel 发送(panic)
close(ch)
ch <- 1 // panic!
// ✅ 正确:发送方关闭 channel
func producer(ch chan<- int) {
defer close(ch) // 发送方负责关闭
for i := 0; i < 10; i++ {
ch <- i
}
}
// ✅ 正确:接收方检测关闭
for val := range ch {
process(val)
}
// 或者
val, ok := <-ch
if !ok {
// channel 已关闭
}2.3 使用 sync.WaitGroup
// ❌ 错误:Add 在 goroutine 内部
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
go func() {
wg.Add(1) // 竞态条件!
defer wg.Done()
work()
}()
}
wg.Wait()
// ✅ 正确:Add 在 goroutine 启动前
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
work()
}()
}
wg.Wait()2.4 避免在循环中捕获变量(Go < 1.22)
// ❌ 错误(Go < 1.22):捕获循环变量
for _, item := range items {
go func() {
process(item) // 所有 goroutine 可能使用同一个 item
}()
}
// ✅ 正确:传递参数
for _, item := range items {
go func(it Item) {
process(it)
}(item)
}
// ✅ Go 1.22+:默认行为已修复,每次迭代创建新变量2.5 Worker Pool 模式
// ✅ 推荐:限制并发数量
func processWithWorkerPool(ctx context.Context, items []Item, workers int) error {
jobs := make(chan Item, len(items))
results := make(chan error, len(items))
// 启动 worker
for w := 0; w < workers; w++ {
go func() {
for item := range jobs {
results <- process(item)
}
}()
}
// 发送任务
for _, item := range items {
jobs <- item
}
close(jobs)
// 收集结果
for range items {
if err := <-results; err != nil {
return err
}
}
return nil
}---
3. Context 使用
3.1 Context 作为第一个参数
// ❌ 错误:context 不是第一个参数
func Process(data []byte, ctx context.Context) error
// ❌ 错误:context 存储在 struct 中
type Service struct {
ctx context.Context // 不要这样做!
}
// ✅ 正确:context 作为第一个参数,命名为 ctx
func Process(ctx context.Context, data []byte) error3.2 传播而非创建新的根 Context
// ❌ 错误:在调用链中创建新的根 context
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() // 丢失了请求的 context!
process(ctx)
next.ServeHTTP(w, r)
})
}
// ✅ 正确:从请求中获取并传播
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, key, value)
process(ctx)
next.ServeHTTP(w, r.WithContext(ctx))
})
}3.3 始终调用 cancel 函数
// ❌ 错误:未调用 cancel
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
// 缺少 cancel() 调用,可能资源泄漏
// ✅ 正确:使用 defer 确保调用
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel() // 即使超时也要调用3.4 响应 Context 取消
// ✅ 推荐:在长时间操作中检查 context
func LongRunningTask(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // 返回 context.Canceled 或 context.DeadlineExceeded
default:
// 执行一小部分工作
if err := doChunk(); err != nil {
return err
}
}
}
}3.5 区分取消原因
// ✅ 根据 ctx.Err() 区分取消原因
if err := ctx.Err(); err != nil {
switch {
case errors.Is(err, context.Canceled):
log.Println("operation was canceled")
case errors.Is(err, context.DeadlineExceeded):
log.Println("operation timed out")
}
return err
}---
4. 接口设计
4.1 接受接口,返回结构体
// ❌ 不推荐:接受具体类型
func SaveUser(db *sql.DB, user User) error
// ✅ 推荐:接受接口(解耦、易测试)
type UserStore interface {
Save(ctx context.Context, user User) error
}
func SaveUser(store UserStore, user User) error
// ❌ 不推荐:返回接口
func NewUserService() UserServiceInterface
// ✅ 推荐:返回具体类型
func NewUserService(store UserStore) *UserService4.2 在消费者处定义接口
// ❌ 不推荐:在实现包中定义接口
// package database
type Database interface {
Query(ctx context.Context, query string) ([]Row, error)
// ... 20 个方法
}
// ✅ 推荐:在消费者包中定义所需的最小接口
// package userservice
type UserQuerier interface {
QueryUsers(ctx context.Context, filter Filter) ([]User, error)
}4.3 保持接口小而专注
// ❌ 不推荐:大而全的接口
type Repository interface {
GetUser(id int) (*User, error)
CreateUser(u *User) error
UpdateUser(u *User) error
DeleteUser(id int) error
GetOrder(id int) (*Order, error)
CreateOrder(o *Order) error
// ... 更多方法
}
// ✅ 推荐:小而专注的接口
type UserReader interface {
GetUser(ctx context.Context, id int) (*User, error)
}
type UserWriter interface {
CreateUser(ctx context.Context, u *User) error
UpdateUser(ctx context.Context, u *User) error
}
// 组合接口
type UserRepository interface {
UserReader
UserWriter
}4.4 避免空接口滥用
// ❌ 不推荐:过度使用 interface{}
func Process(data interface{}) interface{}
// ✅ 推荐:使用泛型(Go 1.18+)
func Process[T any](data T) T
// ✅ 推荐:定义具体接口
type Processor interface {
Process() Result
}---
5. 接收器类型选择
5.1 使用指针接收器的情况
// ✅ 需要修改接收器时
func (u *User) SetName(name string) {
u.Name = name
}
// ✅ 接收器包含 sync.Mutex 等同步原语
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
// ✅ 接收器是大型结构体(避免复制开销)
type LargeStruct struct {
Data [1024]byte
// ...
}
func (l *LargeStruct) Process() { /* ... */ }5.2 使用值接收器的情况
// ✅ 接收器是小型不可变结构体
type Point struct {
X, Y float64
}
func (p Point) Distance(other Point) float64 {
return math.Sqrt(math.Pow(p.X-other.X, 2) + math.Pow(p.Y-other.Y, 2))
}
// ✅ 接收器是基本类型的别名
type Counter int
func (c Counter) String() string {
return fmt.Sprintf("%d", c)
}
// ✅ 接收器是 map、func、chan(本身是引用类型)
type StringSet map[string]struct{}
func (s StringSet) Contains(key string) bool {
_, ok := s[key]
return ok
}5.3 一致性原则
// ❌ 不推荐:混合使用接收器类型
func (u User) GetName() string // 值接收器
func (u *User) SetName(n string) // 指针接收器
// ✅ 推荐:如果有任何方法需要指针接收器,全部使用指针
func (u *User) GetName() string { return u.Name }
func (u *User) SetName(n string) { u.Name = n }---
6. 性能优化
6.1 预分配 Slice
// ❌ 不推荐:动态增长
var result []int
for i := 0; i < 10000; i++ {
result = append(result, i) // 多次分配和复制
}
// ✅ 推荐:预分配已知大小
result := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
result = append(result, i)
}
// ✅ 或者直接初始化
result := make([]int, 10000)
for i := 0; i < 10000; i++ {
result[i] = i
}6.2 避免不必要的堆分配
// ❌ 可能逃逸到堆
func NewUser() *User {
return &User{} // 逃逸到堆
}
// ✅ 考虑返回值(如果适用)
func NewUser() User {
return User{} // 可能在栈上分配
}
// 检查逃逸分析
// go build -gcflags '-m -m' ./...6.3 使用 sync.Pool 复用对象
// ✅ 推荐:高频创建/销毁的对象使用 sync.Pool
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessData(data []byte) string {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
return buf.String()
}6.4 字符串拼接优化
// ❌ 不推荐:循环中使用 + 拼接
var result string
for _, s := range strings {
result += s // 每次创建新字符串
}
// ✅ 推荐:使用 strings.Builder
var builder strings.Builder
for _, s := range strings {
builder.WriteString(s)
}
result := builder.String()
// ✅ 或者使用 strings.Join
result := strings.Join(strings, "")6.5 避免 interface{} 转换开销
// ❌ 热路径中使用 interface{}
func process(data interface{}) {
switch v := data.(type) { // 类型断言有开销
case int:
// ...
}
}
// ✅ 热路径中使用泛型或具体类型
func process[T int | int64 | float64](data T) {
// 编译时确定类型,无运行时开销
}---
7. 测试
7.1 表驱动测试
// ✅ 推荐:表驱动测试
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 1, 2, 3},
{"with zero", 0, 5, 5},
{"negative numbers", -1, -2, -3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}7.2 并行测试
// ✅ 推荐:独立测试用例并行执行
func TestParallel(t *testing.T) {
tests := []struct {
name string
input string
}{
{"test1", "input1"},
{"test2", "input2"},
}
for _, tt := range tests {
tt := tt // Go < 1.22 需要复制
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // 标记为可并行
result := Process(tt.input)
// assertions...
})
}
}7.3 使用接口进行 Mock
// ✅ 定义接口以便测试
type EmailSender interface {
Send(to, subject, body string) error
}
// 生产实现
type SMTPSender struct { /* ... */ }
// 测试 Mock
type MockEmailSender struct {
SendFunc func(to, subject, body string) error
}
func (m *MockEmailSender) Send(to, subject, body string) error {
return m.SendFunc(to, subject, body)
}
func TestUserRegistration(t *testing.T) {
mock := &MockEmailSender{
SendFunc: func(to, subject, body string) error {
if to != "test@example.com" {
t.Errorf("unexpected recipient: %s", to)
}
return nil
},
}
service := NewUserService(mock)
// test...
}7.4 测试辅助函数
// ✅ 使用 t.Helper() 标记辅助函数
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper() // 错误报告时显示调用者位置
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// ✅ 使用 t.Cleanup() 清理资源
func TestWithTempFile(t *testing.T) {
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
os.Remove(f.Name())
})
// test...
}---
8. 常见陷阱
8.1 Nil Slice vs Empty Slice
var nilSlice []int // nil, len=0, cap=0
emptySlice := []int{} // not nil, len=0, cap=0
made := make([]int, 0) // not nil, len=0, cap=0
// ✅ JSON 编码差异
json.Marshal(nilSlice) // null
json.Marshal(emptySlice) // []
// ✅ 推荐:需要空数组 JSON 时显式初始化
if slice == nil {
slice = []int{}
}8.2 Map 初始化
// ❌ 错误:未初始化的 map
var m map[string]int
m["key"] = 1 // panic: assignment to entry in nil map
// ✅ 正确:使用 make 初始化
m := make(map[string]int)
m["key"] = 1
// ✅ 或者使用字面量
m := map[string]int{}8.3 Defer 在循环中
// ❌ 潜在问题:defer 在函数结束时才执行
func processFiles(files []string) error {
for _, file := range files {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close() // 所有文件在函数结束时才关闭!
// process...
}
return nil
}
// ✅ 正确:使用闭包或提取函数
func processFiles(files []string) error {
for _, file := range files {
if err := processFile(file); err != nil {
return err
}
}
return nil
}
func processFile(file string) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
// process...
return nil
}8.4 Slice 底层数组共享
// ❌ 潜在问题:切片共享底层数组
original := []int{1, 2, 3, 4, 5}
slice := original[1:3] // [2, 3]
slice[0] = 100 // 修改了 original!
// original 变成 [1, 100, 3, 4, 5]
// ✅ 正确:需要独立副本时显式复制
slice := make([]int, 2)
copy(slice, original[1:3])
slice[0] = 100 // 不影响 original8.5 字符串子串内存泄漏
// ❌ 潜在问题:子串持有整个底层数组
func getPrefix(s string) string {
return s[:10] // 仍引用整个 s 的底层数组
}
// ✅ 正确:创建独立副本(Go 1.18+)
func getPrefix(s string) string {
return strings.Clone(s[:10])
}
// ✅ Go 1.18 之前
func getPrefix(s string) string {
return string([]byte(s[:10]))
}8.6 Interface Nil 陷阱
// ❌ 陷阱:interface 的 nil 判断
type MyError struct{}
func (e *MyError) Error() string { return "error" }
func returnsError() error {
var e *MyError = nil
return e // 返回的 error 不是 nil!
}
func main() {
err := returnsError()
if err != nil { // true! interface{type: *MyError, value: nil}
fmt.Println("error:", err)
}
}
// ✅ 正确:显式返回 nil
func returnsError() error {
var e *MyError = nil
if e == nil {
return nil // 显式返回 nil
}
return e
}8.7 Time 比较
// ❌ 不推荐:直接使用 == 比较 time.Time
if t1 == t2 { // 可能因为单调时钟差异而失败
// ...
}
// ✅ 推荐:使用 Equal 方法
if t1.Equal(t2) {
// ...
}
// ✅ 比较时间范围
if t1.Before(t2) || t1.After(t2) {
// ...
}---
9. 代码组织
9.1 包命名
// ❌ 不推荐
package common // 过于宽泛
package utils // 过于宽泛
package helpers // 过于宽泛
package models // 按类型分组
// ✅ 推荐:按功能命名
package user // 用户相关功能
package order // 订单相关功能
package postgres // PostgreSQL 实现9.2 避免循环依赖
// ❌ 循环依赖
// package a imports package b
// package b imports package a
// ✅ 解决方案1:提取共享类型到独立包
// package types (共享类型)
// package a imports types
// package b imports types
// ✅ 解决方案2:使用接口解耦
// package a 定义接口
// package b 实现接口9.3 导出标识符规范
// ✅ 只导出必要的标识符
type UserService struct {
db *sql.DB // 私有
}
func (s *UserService) GetUser(id int) (*User, error) // 公开
func (s *UserService) validate(u *User) error // 私有
// ✅ 内部包限制访问
// internal/database/... 只能被同项目代码导入---
10. 工具与检查
10.1 必须使用的工具
# 格式化(必须)
gofmt -w .
goimports -w .
# 静态分析
go vet ./...
# 竞态检测
go test -race ./...
# 逃逸分析
go build -gcflags '-m -m' ./...10.2 推荐的 Linter
# golangci-lint(集成多个 linter)
golangci-lint run
# 常用检查项
# - errcheck: 检查未处理的错误
# - gosec: 安全检查
# - ineffassign: 无效赋值
# - staticcheck: 静态分析
# - unused: 未使用的代码10.3 Benchmark 测试
// ✅ 性能基准测试
func BenchmarkProcess(b *testing.B) {
data := prepareData()
b.ResetTimer() // 重置计时器
for i := 0; i < b.N; i++ {
Process(data)
}
}
// 运行 benchmark
// go test -bench=. -benchmem ./...---
参考资源
Java 代码审查指南
Java 审查重点:Java 17/21 新特性、Spring Boot 3 最佳实践、并发编程(虚拟线程)、JPA 性能优化以及代码可维护性。
目录
---
现代 Java 特性 (17/21+)
记录类(Record)
// ❌ 传统的 POJO/DTO:样板代码多
public class UserDto {
private final String name;
private final int age;
public UserDto(String name, int age) {
this.name = name;
this.age = age;
}
// getters, equals, hashCode, toString...
}
// ✅ 使用 Record:简洁、不可变、语义清晰
public record UserDto(String name, int age) {
// 紧凑构造函数进行验证
public UserDto {
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
}
}Switch 表达式与模式匹配
// ❌ 传统的 Switch:容易漏掉 break,不仅冗长且易错
String type = "";
switch (obj) {
case Integer i: // Java 16+
type = String.format("int %d", i);
break;
case String s:
type = String.format("string %s", s);
break;
default:
type = "unknown";
}
// ✅ Switch 表达式:无穿透风险,强制返回值
String type = switch (obj) {
case Integer i -> "int %d".formatted(i);
case String s -> "string %s".formatted(s);
case null -> "null value"; // Java 21 处理 null
default -> "unknown";
};文本块(Text Blocks)
// ❌ 拼接 SQL/JSON 字符串
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 20\n" +
"}";
// ✅ 使用文本块:所见即所得
String json = """
{
"name": "Alice",
"age": 20
}
""";---
Stream API 与 Optional
避免滥用 Stream
// ❌ 简单的循环不需要 Stream(性能开销 + 可读性差)
items.stream().forEach(item -> {
process(item);
});
// ✅ 简单场景直接用 for-each
for (var item : items) {
process(item);
}
// ❌ 极其复杂的 Stream 链
List<Dto> result = list.stream()
.filter(...)
.map(...)
.peek(...)
.sorted(...)
.collect(...); // 难以调试
// ✅ 拆分为有意义的步骤
var filtered = list.stream().filter(...).toList();
// ...Optional 正确用法
// ❌ 将 Optional 用作参数或字段(序列化问题,增加调用复杂度)
public void process(Optional<String> name) { ... }
public class User {
private Optional<String> email; // 不推荐
}
// ✅ Optional 仅用于返回值
public Optional<User> findUser(String id) { ... }
// ❌ 既然用了 Optional 还在用 isPresent() + get()
Optional<User> userOpt = findUser(id);
if (userOpt.isPresent()) {
return userOpt.get().getName();
} else {
return "Unknown";
}
// ✅ 使用函数式 API
return findUser(id)
.map(User::getName)
.orElse("Unknown");---
Spring Boot 最佳实践
依赖注入 (DI)
// ❌ 字段注入 (@Autowired)
// 缺点:难以测试(需要反射注入),掩盖了依赖过多的问题,且不可变性差
@Service
public class UserService {
@Autowired
private UserRepository userRepo;
}
// ✅ 构造器注入 (Constructor Injection)
// 优点:依赖明确,易于单元测试 (Mock),字段可为 final
@Service
public class UserService {
private final UserRepository userRepo;
public UserService(UserRepository userRepo) {
this.userRepo = userRepo;
}
}
// 💡 提示:结合 Lombok @RequiredArgsConstructor 可简化代码,但要小心循环依赖配置管理
// ❌ 硬编码配置值
@Service
public class PaymentService {
private String apiKey = "sk_live_12345";
}
// ❌ 直接使用 @Value 散落在代码中
@Value("${app.payment.api-key}")
private String apiKey;
// ✅ 使用 @ConfigurationProperties 类型安全配置
@ConfigurationProperties(prefix = "app.payment")
public record PaymentProperties(String apiKey, int timeout, String url) {}---
JPA 与 数据库性能
N+1 查询问题
// ❌ FetchType.EAGER 或 循环中触发懒加载
// Entity 定义
@Entity
public class User {
@OneToMany(fetch = FetchType.EAGER) // 危险!
private List<Order> orders;
}
// 业务代码
List<User> users = userRepo.findAll(); // 1 条 SQL
for (User user : users) {
// 如果是 Lazy,这里会触发 N 条 SQL
System.out.println(user.getOrders().size());
}
// ✅ 使用 @EntityGraph 或 JOIN FETCH
@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllWithOrders();事务管理
// ❌ 在 Controller 层开启事务(数据库连接占用时间过长)
// ❌ 在 private 方法上加 @Transactional(AOP 不生效)
@Transactional
private void saveInternal() { ... }
// ✅ 在 Service 层公共方法加 @Transactional
// ✅ 读操作显式标记 readOnly = true (性能优化)
@Service
public class UserService {
@Transactional(readOnly = true)
public User getUser(Long id) { ... }
@Transactional
public void createUser(UserDto dto) { ... }
}实体(Entity)设计
// ❌ 在 Entity 中使用 Lombok @Data
// @Data 生成的 equals/hashCode 包含所有字段,可能触发懒加载导致性能问题或异常
@Entity
@Data
public class User { ... }
// ✅ 仅使用 @Getter, @Setter
// ✅ 自定义 equals/hashCode (通常基于 ID)
@Entity
@Getter
@Setter
public class User {
@Id
private Long id;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
return id != null && id.equals(((User) o).id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}---
并发与虚拟线程
虚拟线程 (Java 21+)
// ❌ 传统线程池处理大量 I/O 阻塞任务(资源耗尽)
ExecutorService executor = Executors.newFixedThreadPool(100);
// ✅ 使用虚拟线程处理 I/O 密集型任务(高吞吐量)
// Spring Boot 3.2+ 开启:spring.threads.virtual.enabled=true
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
// 在虚拟线程中,阻塞操作(如 DB 查询、HTTP 请求)几乎不消耗 OS 线程资源线程安全
// ❌ SimpleDateFormat 是线程不安全的
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// ✅ 使用 DateTimeFormatter (Java 8+)
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// ❌ HashMap 在多线程环境可能死循环或数据丢失
// ✅ 使用 ConcurrentHashMap
Map<String, String> cache = new ConcurrentHashMap<>();---
Lombok 使用规范
// ❌ 滥用 @Builder 导致无法强制校验必填字段
@Builder
public class Order {
private String id; // 必填
private String note; // 选填
}
// 调用者可能漏掉 id: Order.builder().note("hi").build();
// ✅ 关键业务对象建议手动编写 Builder 或构造函数以确保不变量
// 或者在 build() 方法中添加校验逻辑 (Lombok @Builder.Default 等)---
异常处理
全局异常处理
// ❌ 到处 try-catch 吞掉异常或只打印日志
try {
userService.create(user);
} catch (Exception e) {
e.printStackTrace(); // 不应该在生产环境使用
// return null; // 吞掉异常,上层不知道发生了什么
}
// ✅ 自定义异常 + @ControllerAdvice (Spring Boot 3 ProblemDetail)
public class UserNotFoundException extends RuntimeException { ... }
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ProblemDetail handleNotFound(UserNotFoundException e) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
}
}---
测试规范
单元测试与集成测试
// ❌ 单元测试依赖真实数据库或外部服务
@SpringBootTest // 启动整个 Context,慢
public class UserServiceTest { ... }
// ✅ 单元测试使用 Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock UserRepository repo;
@InjectMocks UserService service;
@Test
void shouldCreateUser() { ... }
}
// ✅ 集成测试使用 Testcontainers
@Testcontainers
@SpringBootTest
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
// ...
}---
审查检查清单
基础与规范
- [ ] 遵循 Java 17/21 新特性(Switch 表达式, Records, 文本块)
- [ ] 避免使用已过时的类(Date, Calendar, SimpleDateFormat)
- [ ] 集合操作是否优先使用了 Stream API 或 Collections 方法?
- [ ] Optional 仅用于返回值,未用于字段或参数
Spring Boot
- [ ] 使用构造器注入而非 @Autowired 字段注入
- [ ] 配置属性使用了 @ConfigurationProperties
- [ ] Controller 职责单一,业务逻辑下沉到 Service
- [ ] 全局异常处理使用了 @ControllerAdvice / ProblemDetail
数据库 & 事务
- [ ] 读操作事务标记了
@Transactional(readOnly = true) - [ ] 检查是否存在 N+1 查询(EAGER fetch 或循环调用)
- [ ] Entity 类未使用 @Data,正确实现了 equals/hashCode
- [ ] 数据库索引是否覆盖了查询条件
并发与性能
- [ ] I/O 密集型任务是否考虑了虚拟线程?
- [ ] 线程安全类是否使用正确(ConcurrentHashMap vs HashMap)
- [ ] 锁的粒度是否合理?避免在锁内进行 I/O 操作
可维护性
- [ ] 关键业务逻辑有充分的单元测试
- [ ] 日志记录恰当(使用 Slf4j,避免 System.out)
- [ ] 魔法值提取为常量或枚举
Qt 代码审查指南
专注于对象模型、信号/槽、事件循环和 GUI 性能的 Qt 代码审查指南。示例基于 Qt 5.15 / Qt 6。
目录
---
对象模型与内存管理
使用父子对象所有权机制
Qt 的 QObject 层次结构会自动管理内存。对于 QObject,优先设置父对象,而不是手动 delete 或使用智能指针。
// ❌ 手动管理容易导致内存泄漏
QWidget* w = new QWidget();
QLabel* l = new QLabel();
l->setParent(w);
// ... 如果 w 被删除,l 会自动被删除。但如果 w 泄漏,l 也会泄漏。
// ✅ 在构造函数中指定父对象
QWidget* w = new QWidget(this); // 归 'this' 所有
QLabel* l = new QLabel(w); // 归 'w' 所有配合 QObject 使用智能指针
如果 QObject 没有父对象,使用 QScopedPointer 或带有自定义删除器的 std::unique_ptr(如果需要跨线程,则用于 deleteLater)。除非必要,否则避免对 QObject 使用 std::shared_ptr,因为它会混淆父子系统的所有权。
// ✅ 用于没有父对象的局部/成员 QObject 的作用域指针
QScopedPointer<MyObject> obj(new MyObject());
// ✅ 防止悬空指针的安全指针
QPointer<MyObject> safePtr = obj.data();
if (safePtr) {
safePtr->doSomething();
}使用 deleteLater()
对于异步删除,尤其是在槽或事件处理程序中,请使用 deleteLater() 而不是 delete,以确保存储在事件循环中的待处理事件能够处理完毕。
---
信号与槽
优先使用函数指针语法
使用编译时检查的语法(Qt 5+)。
// ❌ 基于字符串(仅运行时检查,速度较慢)
connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(updateValue(int)));
// ✅ 编译时检查
connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue);连接类型
跨线程时要明确或注意连接类型。
Qt::AutoConnection(默认):如果同线程则直连,不同线程则队列连接。Qt::QueuedConnection: 始终投递事件(跨线程安全)。Qt::DirectConnection: 立即调用(如果跨线程访问非线程安全数据则很危险)。
避免循环
检查可能导致无限信号循环的逻辑(例如 valueChanged -> setValue -> valueChanged)。在设置值之前阻塞信号或检查相等性。
void MyClass::setValue(int v) {
if (m_value == v) return; // ? Good: 打破循环
m_value = v;
emit valueChanged(v);
}---
容器与字符串
QString 效率
- 使用
QStringLiteral("...")进行编译时字符串创建,避免运行时分配。 - 使用
QLatin1String与 ASCII 字面量进行比较(在 Qt 5 中)。 - 优先使用
arg()进行格式化(或QStringBuilder的%运算符)。
// ❌ 运行时转换
if (str == "test") ...
// ✅ 优先使用 QLatin1String 与 ASCII 字面量进行比较(在 Qt 5 中)
if (str == QLatin1String("test")) ... // Qt 5
if (str == u"test"_s) ... // Qt 6容器选择
- Qt 6:
QList现在是默认选择(与QVector统一)。 - Qt 5: 优先使用
QVector而不是QList,以获得连续内存和缓存性能,除非需要稳定的引用。 - 注意隐式共享(写时复制)。按值传递容器很便宜,直到发生修改。只读访问优先使用
const &。
// ❌ 如果函数修改 'list',则强制深拷贝
void process(QVector<int> list) {
list[0] = 1;
}
// ✅ 只读引用
void process(const QVector<int>& list) { ... }---
线程与并发
子类化 QThread 与 Worker 对象
优先使用 "Worker 对象" 模式,而不是子类化 QThread 的实现细节。
// ❌ 业务逻辑在 QThread::run() 内部
class MyThread : public QThread {
void run() override { ... }
};
// ✅ Worker 对象移动到线程
QThread* thread = new QThread;
Worker* worker = new Worker;
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::process);
thread->start();GUI 线程安全
切勿 从后台线程访问 UI 控件(QWidget 及其子类)。使用信号/槽将更新通信到主线程。
---
图形界面与控件
逻辑分离
将业务逻辑保留在 UI 类(MainWindow, Dialog)之外。UI 类应仅处理显示和用户输入转发。
布局
避免固定大小(setGeometry, resize)。使用布局(QVBoxLayout, QGridLayout)来优雅地处理不同的 DPI 和窗口大小调整。
阻塞事件循环
切勿在主线程中执行长时间运行的操作(导致 GUI 冻结)。
- Bad:
Sleep(),while(busy), 同步网络调用。 - Good:
QProcess,QThread,QtConcurrent, 或异步 API(QNetworkAccessManager)。
---
元对象系统
属性与枚举
对暴露给 QML 或需要内省的值使用 Q_PROPERTY。 使用 Q_ENUM 启用枚举的字符串转换。
class MyObject : public QObject {
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
enum State { Idle, Running };
Q_ENUM(State)
// ...
};qobject_cast
对 QObject 使用 qobject_cast<T*> 而不是 dynamic_cast。它更快且不需要 RTTI。
---
审查清单
- [ ] 内存: 父子关系是否正确?是否避免了悬空指针(使用
QPointer)? - [ ] 信号: 连接是否已检查?Lambda 表达式是否使用了安全的捕获(上下文对象)?
- [ ] 线程: UI 是否仅从主线程访问?长任务是否已卸载?
- [ ] 字符串: 是否适当地使用了
QStringLiteral或tr()? - [ ] 风格: 命名约定(方法使用 camelCase,类使用 PascalCase)。
- [ ] 资源: 资源(图像、样式)是否从
.qrc加载?
PR 审查模板
可直接复制本模板用于代码审查评论。
---
审查概要
[用 1-2 句话概述本次审查范围与结论]
PR 规模: [小 / 中 / 大](约 X 行) 审查耗时: [X 分钟]
亮点
- [做得好的点]
- [采用了哪些好模式]
- [相较历史实现的改进]
必须修改项
🔴 [blocking] [问题描述]
[代码位置或示例]
[建议修复方式或原因]
🔴 [blocking] [问题描述]
[补充细节]
重要建议
🟡 [important] [问题描述]
[为什么重要]
[建议方案]
次要建议
🟢 [nit] [小改进建议]
💡 [suggestion] [可选替代实现]
待澄清问题
❓ [需要澄清的问题 X]
❓ [关于设计决策 Y 的问题]
安全检查
- [ ] 无硬编码密钥/凭证
- [ ] 输入参数有校验
- [ ] 权限校验存在且正确
- [ ] 无 SQL 注入/XSS 风险
测试覆盖
- [ ] 单元测试已新增或更新
- [ ] 边界场景已覆盖
- [ ] 错误路径已覆盖
结论
[ ] ✅ Approve - 可合并 [ ] 💬 Comment - 有建议但不阻断合并 [ ] 🔄 Request Changes - 存在阻断问题,需修复后再审
---
快速复制片段
阻断问题
🔴 **[blocking]** [标题]
[问题描述]
**位置:** `file.ts:123`
**建议修复:**// 建议代码
重要建议
🟡 **[important]** [标题]
[为什么这个问题重要]
**建议:**
- 方案 A:[描述]
- 方案 B:[描述]次要建议
🟢 **[nit]** [建议]
不阻断合并,但建议优化:[说明]正向反馈
🎉 **[praise]** [具体亮点] 做得很好!
[为什么好]澄清问题
❓ **[question]** [你的问题]
我想确认 [X] 的设计决策,是否考虑过 [Y]?