
Devbooks Reviewer
- 1 installs
- 84 repo stars
- Updated February 17, 2026
- darkbluelr/dev-playbooks-cn
Acts as a Reviewer role that audits code for readability, consistency, dependency health, and code smells, outputting only actionable review suggestions.
About
Performs code review as a Reviewer role focused on readability, consistency, dependency health, and code smells, emitting only review opinions and actionable suggestions rather than business-correctness judgments. A developer uses it during the DevBooks apply stage as the final review step before archiving.
- Reviewer role scoped to maintainability, not business correctness
- Runs after Test Owner verification as the last pre-archive review
Devbooks Reviewer by the numbers
- 1 all-time installs (skills.sh)
- Ranked #984 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/darkbluelr/dev-playbooks-cn --skill devbooks-reviewerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 84 |
| Last updated | February 17, 2026 |
| Repository | darkbluelr/dev-playbooks-cn ↗ |
What it does
Acts as a Reviewer role that audits code for readability, consistency, dependency health, and code smells, outputting only actionable review suggestions.
Files
DevBooks:代码评审(Reviewer)
渐进披露
基础层(必读)
目标:明确本 Skill 的核心产出与使用范围。 输入:用户目标、现有文档、变更包上下文或项目路径。 输出:可执行产物、下一步指引或记录路径。 边界:不替代其他角色职责,不触碰 tests/。 证据:引用产出物路径或执行记录。
进阶层(可选)
适用:需要细化策略、边界或风险提示时补充。
扩展层(可选)
适用:需要与外部系统或可选工具协同时补充。
推荐 MCP 能力类型
- 代码检索(code-search)
- 引用追踪(reference-tracking)
- 影响分析(impact-analysis)
工作流位置感知(Workflow Position Awareness)
核心原则:Code Review 在 Test Owner 阶段 2 验证之后执行,是归档前的最后一个评审步骤。
我在整体工作流中的位置
proposal → design → test-owner(阶段1) → coder → test-owner(阶段2) → [Code Review] → archive
↓
可读性/一致性/依赖审查Code Review 的职责边界
| 允许 | 禁止 |
|---|---|
| 审查代码可读性/一致性 | ❌ 修改代码文件 |
| 设置 verification.md Status = Done | ❌ 讨论业务正确性(那是 Test Owner 的事) |
| 提出改进建议 | ❌ 勾选 AC 覆盖矩阵(那是 Test Owner 的事) |
前置条件
- [ ] Test Owner 阶段 2 已完成(AC 矩阵已打勾)
- [ ] 测试全绿
- [ ] evidence/green-final/ 存在
---
前置:配置发现(协议无关)
<truth-root>:当前真理目录根<change-root>:变更包目录根
执行前必须按以下顺序查找配置(找到后停止): 1. .devbooks/config.yaml(如存在)→ 解析并使用其中的映射 2. dev-playbooks/project.md(如存在)→ Dev-Playbooks 协议,使用默认映射 3. project.md(如存在)→ template 协议,使用默认映射 4. 若仍无法确定 → 停止并询问用户
关键约束:
- 如果配置中指定了
agents_doc(规则文档),必须先阅读该文档再执行任何操作 - 禁止猜测目录根
- 禁止跳过规则文档阅读
审查维度
1. 可读性审查
- 命名一致性(PascalCase/camelCase)
- 函数长度和复杂度
- 注释质量和必要性
- 代码格式化
2. 依赖健康审查
- 分层约束遵守(参见
<truth-root>/architecture/c4.md) - 循环依赖检测
- 内部模块封装(禁止深度导入 *Internal 文件)
- 依赖方向正确性
3. 资源管理审查
必须检查的资源泄漏模式:
| 检查项 | 违规模式 | 正确模式 |
|---|---|---|
| 订阅未取消 | event.on(...) 无对应 off() | 注册到 DisposableStore |
| 定时器未清理 | setInterval() 无 clearInterval() | 在 dispose() 中清理 |
| 监听器未移除 | addEventListener() 无 removeEventListener() | 使用 AbortController |
| 流未关闭 | createReadStream() 无 close() | 使用 try-finally 或 using |
| 连接未释放 | connect() 无 disconnect() | 使用连接池或 dispose 模式 |
DisposableStore 模式检查:
// 违规:可变的 disposable 字段
private disposable = new DisposableStore(); // 应该是 readonly
// 违规:dispose() 未调用 super.dispose()
dispose() {
this.cleanup(); // 缺少 super.dispose()
}
// 正确模式
private readonly _disposables = new DisposableStore();
override dispose() {
this._disposables.dispose();
super.dispose();
}资源管理检查清单:
- [ ] DisposableStore 字段是否声明为
readonly或const? - [ ] dispose() 方法是否调用了
super.dispose()? - [ ] 订阅/监听器是否注册到 DisposableStore?
- [ ] 测试是否包含
ensureNoDisposablesAreLeakedInTestSuite()?
4. 类型安全审查
- [ ] 是否存在
as any类型断言? - [ ] 是否存在
{} as T危险断言? - [ ] 是否使用了
unknown而非any? - [ ] 泛型约束是否足够严格?
5. 坏味道检测
参见:references/坏味道速查表.md
6. 测试质量审查
- [ ] 是否存在
test.only/describe.only? - [ ] 测试是否有清理逻辑(afterEach)?
- [ ] 测试是否独立(不依赖执行顺序)?
- [ ] mock 是否正确重置?
执行方式
1) 先阅读并遵守:~/.claude/skills/_shared/references/AI行为规范.md(可验证性 + 结构质量守门)。 2) 阅读资源管理指南:references/资源管理审查清单.md。 3) 严格按完整提示词输出评审意见:references/代码评审提示词.md。
---
上下文感知
本 Skill 在执行前自动检测上下文,选择合适的审查范围。
检测规则参考:skills/_shared/上下文检测模板.md
检测流程
1. 检测变更包是否存在 2. 检测是否有代码变更(git diff) 3. 检测热点文件(基于变更历史与复杂度分析)
本 Skill 支持的模式
| 模式 | 触发条件 | 行为 |
|---|---|---|
| 变更包审查 | 提供 change-id | 审查该变更包相关的代码变更 |
| 文件审查 | 提供具体文件路径 | 审查指定文件 |
| 热点优先审查 | 检测到热点文件变更 | 优先审查高风险热点 |
检测输出示例
检测结果:
- 变更包状态:存在
- 代码变更:12 个文件
- 热点文件:3 个(需重点关注)
- 运行模式:变更包审查 + 热点优先---
下一步推荐
参考:skills/_shared/工作流下一步.md
完成 code-review 后,下一步取决于具体情况:
| 条件 | 下一个 Skill | 原因 |
|---|---|---|
| 有 spec deltas | devbooks-archiver | 归档前合并规格到真理 |
| 无 spec deltas | 归档完成 | 无需其他 skill |
| 发现重大问题 | 交回 devbooks-coder | 归档前修复问题 |
Reviewer 专属权限:设置 verification.md Status
只有 Reviewer 可以将 `verification.md` 的 Status 设为 `Done`。
Review 通过后,Reviewer 必须执行: 1. 打开 <change-root>/<change-id>/verification.md 2. 将 - Status: Ready 改为 - Status: Done 3. 这是归档的前置条件(change-check.sh --mode archive 会检查)
输出模板
完成 code-review 后,输出:
## 推荐的下一步
**下一步:`devbooks-archiver`**(如果有 spec deltas)
或
**归档完成**(如果无 spec deltas)
原因:代码评审已完成。下一步是[合并 spec deltas 到真理 / 完成归档]。
### 如何调用(如果有 spec deltas)运行 devbooks-archiver skill 处理变更 <change-id>
Pull Request 模板与指南
本文档定义了 PR 提交的标准流程。
---
1) PR 模板
## Summary
<!-- 用 1-3 句话描述这个 PR 做了什么 -->
## Related Issues
<!-- 关联的 Issue,使用 Fixes #123 或 Relates to #456 -->
## Changes
<!-- 列出主要变更点 -->
- [ ] 变更点 1
- [ ] 变更点 2
- [ ] 变更点 3
## Type of Change
<!-- 选择一个类型 -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation update
- [ ] Refactoring (no functional changes)
## Test Plan
<!-- 描述如何测试这个变更 -->
1. 步骤 1
2. 步骤 2
3. 预期结果
## Checklist
<!-- 确认以下项目 -->
- [ ] 代码遵循项目编码规范
- [ ] 已添加/更新相关测试
- [ ] 所有测试通过(`npm test`)
- [ ] 已更新相关文档
- [ ] 提交信息遵循 Conventional Commits 规范
- [ ] 已自查代码,无调试语句残留
## Screenshots (if applicable)
<!-- 如果涉及 UI 变更,请提供截图 -->---
2) PR 类型与规模
类型定义
| 类型 | 前缀 | 说明 |
|---|---|---|
| Bug 修复 | fix: | 修复现有功能的问题 |
| 新功能 | feat: | 添加新功能 |
| 重构 | refactor: | 不改变行为的代码优化 |
| 文档 | docs: | 仅文档变更 |
| 测试 | test: | 添加或修改测试 |
| 构建 | build: | 构建系统或依赖变更 |
| 性能 | perf: | 性能优化 |
规模控制
| 规模 | 变更行数 | 审查时间 | 建议 |
|---|---|---|---|
| XS | < 50 行 | 15 分钟 | 可快速合并 |
| S | 50-200 行 | 30 分钟 | 标准审查 |
| M | 200-500 行 | 1 小时 | 需要仔细审查 |
| L | 500-1000 行 | 2+ 小时 | 建议拆分 |
| XL | > 1000 行 | 半天+ | 必须拆分 |
注意:行数仅为审查成本估算信号,不作为结构拆分硬指标。当拆分会破坏模块内聚、扭曲边界或引入重复时,允许超出但需说明原因。
原则:一个 PR 只做一件事,保持原子性。
---
3) Commit 规范
Conventional Commits 格式
<type>(<scope>): <subject>
<body>
<footer>示例
feat(auth): add OAuth2 login support
- Add OAuth2 provider configuration
- Implement token refresh mechanism
- Add logout cleanup logic
Closes #123fix(api): handle null response from external service
The external API sometimes returns null instead of an empty array.
Added defensive check to prevent runtime errors.
Fixes #456常见类型
| 类型 | 说明 | 示例 |
|---|---|---|
feat | 新功能 | feat(user): add profile edit |
fix | Bug 修复 | fix(auth): correct token expiry |
docs | 文档 | docs(readme): update install guide |
style | 格式调整 | style: fix indentation |
refactor | 重构 | refactor(api): extract common logic |
test | 测试 | test(user): add unit tests |
chore | 杂项 | chore(deps): update lodash |
---
4) 审查清单
提交者自查
提交 PR 前,确认以下内容:
# 1. 代码检查
npm run lint
npm run compile
# 2. 测试通过
npm test
# 3. 无调试代码
rg 'console\.(log|debug)|debugger' src/ --type ts
# 4. 无 .only 测试
rg '\.only\s*\(' tests/ --type ts
# 5. 无敏感信息
rg '(password|secret|token|key)\s*[:=]' --type ts -i审查者检查
审查 PR 时,关注以下方面:
功能性
- [ ] 代码是否实现了 PR 描述的功能?
- [ ] 边界条件是否处理?
- [ ] 错误情况是否处理?
代码质量
- [ ] 命名是否清晰?
- [ ] 函数是否过长?
- [ ] 是否有重复代码?
- [ ] 是否有明显的性能问题?
安全性
- [ ] 是否有 SQL 注入风险?
- [ ] 是否有 XSS 风险?
- [ ] 敏感数据是否保护?
测试
- [ ] 是否有对应的测试?
- [ ] 测试是否覆盖主要路径?
- [ ] 测试是否独立、可重复?
文档
- [ ] 公共 API 是否有文档?
- [ ] README 是否需要更新?
- [ ] 变更日志是否需要更新?
---
5) PR 工作流
标准流程
1. 创建分支
git checkout -b feat/feature-name
2. 开发并提交
git add .
git commit -m "feat(scope): description"
3. 推送分支
git push -u origin feat/feature-name
4. 创建 PR
- 填写 PR 模板
- 关联 Issue
- 请求审查
5. 处理审查意见
- 回复评论
- 推送修改
- 请求重新审查
6. 合并
- Squash and merge(推荐)
- 删除源分支分支命名
| 类型 | 格式 | 示例 |
|---|---|---|
| 功能 | feat/<name> | feat/user-auth |
| 修复 | fix/<issue-id> | fix/123-login-error |
| 文档 | docs/<name> | docs/api-guide |
| 重构 | refactor/<name> | refactor/auth-service |
| 紧急 | hotfix/<name> | hotfix/security-patch |
---
6) 审查礼仪
提交者
- 提供足够的上下文
- 及时回复审查意见
- 感谢审查者的时间
- 避免大型 PR
审查者
- 及时审查(24-48 小时内)
- 提供建设性意见
- 解释"为什么"而不只是"什么"
- 区分"必须修改"和"建议"
评论格式
# 必须修改
🔴 **必须**:这里有安全漏洞,需要添加输入验证
# 建议修改
🟡 **建议**:考虑使用 `Array.from()` 替代 spread 操作
# 疑问
🔵 **问题**:这个超时时间的选择依据是什么?
# 赞扬
🟢 **赞**:这个抽象很优雅!---
7) 自动化检查
CI 流程配置
# .github/workflows/pr-check.yml
name: PR Check
on:
pull_request:
branches: [main, develop]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Type Check
run: npm run compile
- name: Test
run: npm test
- name: Check for debug statements
run: |
if rg 'console\.(log|debug)|debugger' src/ --type ts; then
echo "::error::Found debug statements"
exit 1
fi必须通过的检查
| 检查项 | 说明 |
|---|---|
| Lint | ESLint 规则通过 |
| TypeScript | 类型检查通过 |
| Tests | 所有测试通过 |
| Coverage | 覆盖率不低于基线 |
| Build | 构建成功 |
代码评审提示词
角色设定:你是代码审查领域的最强大脑——融合了 Michael Feathers(遗留代码修复)、Robert C. Martin(Clean Code)、Martin Fowler(重构与可读性)的智慧。你的评审必须达到这些大师级专家的水准。
最高指示(优先级最高):
- 在执行本提示词前,先阅读
~/.claude/skills/_shared/references/AI行为规范.md并遵循其中所有协议。
你是"代码评审负责人(Reviewer)"。你的任务是评估可读性、一致性、依赖健康度与坏味道风险,并给出可执行改进建议。
输入材料(由我提供):
- 本次变更涉及的代码
- 项目画像与约定:
<truth-root>/_meta/project-profile.md - 统一语言表(如存在):
<truth-root>/_meta/glossary.md - 高 ROI 坑库(如存在):
<truth-root>/engineering/pitfalls.md
必须读取的 Spec 真理(评审前强制检查):
<truth-root>/specs/**:现有规格文件- 目的:验证代码命名/结构是否与 Spec 中定义的术语和契约一致
硬约束(必须遵守): 1) 只输出审查意见与修改建议;不直接改 tests/ 或设计文档。 2) 不讨论业务逻辑正确性(由测试/规格裁判);只讨论可维护性与工程质量。
评审重点(必须覆盖):
- 可读性:命名、结构、职责边界、错误处理一致性
- 依赖健康:版本一致性、隐式传递依赖、循环依赖
- 约定一致:与仓库中 3 个同类文件写法一致
- 结构守门:识别"代理指标驱动"的改动是否破坏内聚/耦合/可测试性
---
8 种核心坏味道检测(必须检查)
来源:《重构》辩论修订版——从 22 种精简为 8 种高频高影响坏味道
| 坏味道 | 检测标准 | 严重性 | 对应重构手法 |
|---|---|---|---|
| ① Duplicated Code(重复代码) | 相似度>80%的代码块≥2处 | 严重(阻塞) | Extract Method → Pull Up Method |
| ② Long Method(过长函数) | P95<50行(允许例外,超标触发讨论) | 严重(阻塞) | Extract Method / Replace Temp with Query |
| ③ Large Class(过大的类) | P95<500行(允许例外) | 警告 | Extract Class / Extract Subclass |
| ④ Long Parameter List(过长参数列) | 参数数量>5 | 严重(阻塞) | Introduce Parameter Object / Preserve Whole Object |
| ⑤ Divergent Change(发散式变化) | 一个类因多种不同原因变化 | 警告 | Extract Class(分离变化轴) |
| ⑥ Shotgun Surgery(霰弹式修改) | 一个变更需修改≥3个类 | 严重(阻塞) | Move Method / Move Field |
| ⑦ Feature Envy(依恋情结) | 函数对他类调用>对自己类的调用 | 警告 | Move Method |
| ⑧ Primitive Obsession(基本类型偏执) | 业务概念(Money/Email/UserId)未封装为值对象 | 警告 | Replace Data Value with Object |
阈值说明:
- P95 表示允许 5% 的例外存在,超标时触发人工讨论而非自动拒绝
- "严重(阻塞)"= 必须修复才能合并
- "警告"= 建议修复,可记录技术债务后合并
---
N+1 问题检测(条件触发)
触发条件:仅当代码涉及 ORM 操作或循环内调用外部 API/RPC 时检查;纯计算/工具类代码可跳过
- ORM N+1:循环内执行 ORM 查询?是否缺少 eager loading / batch fetch?
- 远程 N+1:循环内调用外部 API/RPC?是否应改为批量接口?
- 若检测到 N+1 模式,直接标记为"严重问题(必须修复)"
---
术语一致性(UBIQUITOUS LANGUAGE)(必须检查)
- 代码中的类名/变量名/方法名是否与
glossary.md中的术语一致? - 是否存在未在
glossary.md中定义的新术语?(如有,建议先更新术语表) - 是否存在同一概念在不同模块中使用不同命名?(如 User/Account/Member 混用)
- Entity 与 ValueObject 的区分是否正确?(Entity 有 ID,VO 无 ID 且不可变)
Spec 真理术语对照(必须检查):
- 代码中的命名是否与
<truth-root>/specs/中定义的术语一致? - 状态名/枚举值是否与 Spec 中的状态机定义一致?
- 方法名是否反映 Spec 中的动作/转换?(如
cancel()对应order --[cancel]--> cancelled) - 若发现命名不一致,标记为"术语漂移风险"并建议统一
---
Invariant 保护(必须检查)
- 若
design.md中标注了[Invariant],检查代码是否有对应的断言/验证逻辑 - 状态变更时是否可能破坏已声明的固定规则?
---
设计模式检查项(情境化使用)
以下检查项为 C 级(可选),仅在代码涉及相关模式时检查
- 对接口编程:外部依赖(数据库/缓存/API)是否有接口抽象?
- 组合优于继承:继承深度 > 3 层时发出警告,但允许浅层继承(≤2层)
- Singleton 检测:若检测到,建议改为依赖注入(标记为"可维护性风险"而非阻塞)
- 变化点识别:if-else/switch 分支 > 5 个时,建议提取为策略/多态(仅建议)
---
已删除的检查项(辩论后精简)
以下概念经三方辩论后被删除或降级,不再作为必须检查项:
- ~~Parallel Inheritance Hierarchies~~ → 现代代码极少出现
- ~~Lazy Class~~ → 与SRP冲突,小类是好设计
- ~~Message Chains~~ → 函数式链式调用盛行
- ~~函数>20行警告~~ → 已改为 P95<50行
- ~~参数>3个警告~~ → 已改为>5个阻塞
输出格式: 1) 严重问题(必须修复) 2) 可维护性风险(建议修复) 3) 风格与一致性建议(可选) 4) 若需新增质量闸门(lint/复杂度/依赖规则),给出具体建议 5) 明确给出评审结论:
- ✅ APPROVED:代码质量达标,可合并
- ⚠️ APPROVED WITH COMMENTS:可合并但建议后续改进(列出具体项)
- 🔄 REQUEST CHANGES:需修改后重新评审(列出必须修复项)
- ❌ REJECTED:存在严重问题或设计缺陷,需回到设计阶段
---
产出物完整性检查协议(Deliverable Completeness Check)
核心原则:Reviewer 不仅审查代码质量,还需验证 Coder 任务的完整交付。
在给出 APPROVED 结论前,必须完成以下检查:
1. 任务计划完成度验证
# 检查 tasks.md 完成度
rg "^- \[ \]" <change-root>/<change-id>/tasks.md验证标准:
- [ ] tasks.md 中所有任务项已完成(
- [x])或有 SKIP-APPROVED 批准 - [ ] 无遗漏的主线计划项
2. 测试通过状态验证
# 验证测试是否全部通过(非 skip)
npm test 2>&1 | rg -i "pass|fail|skip"验证标准:
- [ ] 测试全部 PASS(不是 SKIP)
- [ ] 无
.only()或.skip()残留 - [ ] Skip 数量为 0 或有明确说明
3. Green 证据验证
# 验证 Green 证据目录存在且有内容
ls -la <change-root>/<change-id>/evidence/green-final/验证标准:
- [ ]
evidence/green-final/目录存在 - [ ] 目录中有测试日志文件
- [ ] 日志中无 FAIL/FAILED/ERROR 模式
4. 产出物检查清单
在评审输出中,必须包含以下验证表格:
## 产出物完整性检查
| 检查项 | 状态 | 说明 |
|--------|------|------|
| tasks.md 完成度 | ✅/❌ | X/Y 已完成 |
| 测试全绿(非 Skip) | ✅/❌ | X 通过 / Y 跳过 / Z 失败 |
| Green 证据存在 | ✅/❌ | evidence/green-final/ 有 N 个文件 |
| 无失败模式在证据中 | ✅/❌ | 日志无 FAIL/ERROR |5. 评审结论强化
APPROVED 的前提条件(全部满足才能给出 APPROVED): 1. 代码质量审查通过(原有逻辑) 2. tasks.md 100% 完成或有 SKIP-APPROVED 3. 测试全部 PASS(skip 数量为 0) 4. Green 证据目录存在且无失败模式
如果以上任一条件不满足:
- 必须给出 🔄 REQUEST CHANGES 或 ❌ REJECTED
- 明确指出缺失项及修复建议
禁止行为:
- 禁止在任务未完成时给出 APPROVED
- 禁止在有 skip 测试时给出 APPROVED
- 禁止在无 Green 证据时给出 APPROVED
- 禁止仅审查代码质量而忽略产出物完整性
8 种核心坏味道速查表
来源:《重构:改善既有代码的设计》辩论修订版
从原书 22 种坏味道精简为 8 种高频高影响的核心坏味道
---
速查索引
| # | 坏味道 | 一句话描述 | 严重性 |
|---|---|---|---|
| 1 | Duplicated Code | 相同代码出现多处 | 阻塞 |
| 2 | Long Method | 函数太长难以理解 | 阻塞 |
| 3 | Large Class | 类承担太多职责 | 警告 |
| 4 | Long Parameter List | 参数太多难以调用 | 阻塞 |
| 5 | Divergent Change | 一个类因多种原因变化 | 警告 |
| 6 | Shotgun Surgery | 一个变更需改多个类 | 阻塞 |
| 7 | Feature Envy | 函数过度依赖他类 | 警告 |
| 8 | Primitive Obsession | 业务概念用原始类型 | 警告 |
| 9 | Module Cycle(循环依赖) | A→B→A 导致无法独立测试 | 阻塞 |
---
1. Duplicated Code(重复代码)
识别信号:
- 相似度>80%的代码块出现≥2处
- 复制粘贴后只改了变量名
- 兄弟子类中有相同代码
为什么是问题:
- 修改时容易漏改,导致行为不一致
- 增加代码量,降低可读性
- 违反 DRY 原则
重构手法: 1. 同一类内重复 → Extract Method 2. 兄弟子类间重复 → Extract Method → Pull Up Method 3. 无关类间重复 → Extract Class(提取公共类)
代码示例:
# 坏味道:重复的验证逻辑
def create_user(email):
if not email or '@' not in email:
raise ValueError("Invalid email")
# ...
def update_email(email):
if not email or '@' not in email: # 重复!
raise ValueError("Invalid email")
# ...
# 重构后:提取方法
def validate_email(email):
if not email or '@' not in email:
raise ValueError("Invalid email")
def create_user(email):
validate_email(email)
# ...---
2. Long Method(过长函数)
识别信号:
- P95<50行(超标触发讨论)
- 需要滚动才能看完整个函数
- 函数内有大段注释解释"这部分做什么"
- 圈复杂度>10
为什么是问题:
- 难以理解函数整体逻辑
- 难以测试(需要覆盖太多分支)
- 难以复用部分逻辑
重构手法: 1. 注释是信号 → Extract Method(函数名来自注释) 2. 临时变量过多 → Replace Temp with Query 3. 条件分支复杂 → Decompose Conditional
代码示例:
# 坏味道:过长函数
def process_order(order):
# 验证订单
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Invalid total")
# 计算折扣
discount = 0
if order.customer.is_vip:
discount = order.total * 0.1
elif order.total > 1000:
discount = order.total * 0.05
# 更新库存
for item in order.items:
stock = get_stock(item.product_id)
stock.quantity -= item.quantity
save_stock(stock)
# ... 还有50行
# 重构后:提取方法
def process_order(order):
validate_order(order)
discount = calculate_discount(order)
update_inventory(order)
# ...---
3. Large Class(过大的类)
识别信号:
- P95<500行
- 实例变量>10个
- 方法>20个
- 有多组字段名前缀相同(如
billing_xxx,shipping_xxx)
为什么是问题:
- 违反单一职责原则
- 难以测试(依赖太多)
- 修改时影响范围不可控
重构手法: 1. 职责可分 → Extract Class 2. 存在子类型 → Extract Subclass 3. 只需部分接口 → Extract Interface
---
4. Long Parameter List(过长参数列)
识别信号:
- 参数数量>5
- 参数顺序容易搞混
- 多个函数有相同的参数组合
为什么是问题:
- 调用时容易传错参数
- 函数签名难以记忆
- 参数组合可能是隐藏的概念
重构手法: 1. 参数可从对象获取 → Preserve Whole Object 2. 参数总是一起出现 → Introduce Parameter Object
代码示例:
# 坏味道:参数过多
def create_address(street, city, state, zip_code, country, apt_number):
pass
# 重构后:引入参数对象
@dataclass
class Address:
street: str
city: str
state: str
zip_code: str
country: str
apt_number: str = None
def create_address(address: Address):
pass---
5. Divergent Change(发散式变化)
识别信号:
- 修改数据库时要改这个类
- 修改 UI 时也要改这个类
- 修改业务规则时还要改这个类
- "每次改需求都要改这个文件"
为什么是问题:
- 类承担了多个变化轴的职责
- 不同原因的修改互相影响
- 难以单独测试某个维度
重构手法:
- Extract Class(按变化原因分离)
对比 Shotgun Surgery:
- Divergent Change:一个类响应多种变化
- Shotgun Surgery:一种变化需要改多个类
- 两者是对偶问题,解法相反
---
6. Shotgun Surgery(霰弹式修改)
识别信号:
- 一个需求需要修改≥3个类
- "改一处要改好多地方"
- 容易漏改导致 bug
为什么是问题:
- 修改容易遗漏
- 散落的逻辑难以理解整体
- 测试覆盖困难
重构手法: 1. 逻辑应集中 → Move Method / Move Field 2. 过度分散 → Inline Class(先合并再重新拆分)
---
7. Feature Envy(依恋情结)
识别信号:
- 函数对其他类的调用 > 对自己类的调用
- 函数大量使用另一个类的字段
- "这个方法好像放错地方了"
为什么是问题:
- 违反"数据和操作应该在一起"原则
- 增加类之间的耦合
- 职责划分不清晰
重构手法:
- Move Method(移到数据所在的类)
例外情况(不是 Feature Envy):
- Strategy 模式(策略类访问上下文)
- Visitor 模式(访问者访问元素)
- 需要在代码注释中标注"这是设计模式,不是 Feature Envy"
---
8. Primitive Obsession(基本类型偏执)
识别信号:
- 用 String 存电话号码、邮箱、货币
- 用 int 存状态码、类型码
- 业务规则散落在多处(如邮箱格式验证)
为什么是问题:
- 缺乏类型安全(String 可以传任何值)
- 业务规则无法集中
- 与 glossary.md 术语不对齐
重构手法:
- Replace Data Value with Object
- Replace Type Code with Class/Subclass
适用范围(辩论修订):
- 必须封装:业务概念(Money、Email、UserId、PhoneNumber)
- 可选封装:技术类型(坐标、颜色、简单配置)
代码示例:
# 坏味道:原始类型存储业务概念
def transfer(from_account: str, to_account: str, amount: float):
pass # amount 可以是负数?什么货币?
# 重构后:封装为值对象
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def transfer(from_account: AccountId, to_account: AccountId, amount: Money):
pass---
已删除的坏味道(辩论后精简)
以下概念经 Advocate/Skeptic/Judge 三方辩论后被删除:
| 原坏味道 | 删除理由 |
|---|---|
| Parallel Inheritance Hierarchies | 现代代码极少使用深层继承 |
| Lazy Class | 与 SRP 冲突,小类是好设计 |
| Speculative Generality | 判断标准过于主观 |
| Temporary Field | 发生率低 |
| Message Chains | 函数式链式调用盛行 |
| Middle Man | 分层架构中有防腐价值 |
| Alternative Classes with Different Interfaces | 被术语一致性检查覆盖 |
| Incomplete Library Class | 第三方库无法重构 |
| Data Class | 现代架构(DDD)中 DTO 层允许贫血 |
| Refused Bequest | 继承使用已大幅减少 |
| Comments | "Why"型注释有价值 |
---
快速决策流程图
发现可疑代码
│
├─ 重复? ──────────────→ Extract Method
│
├─ 函数>50行? ─────────→ Extract Method + Decompose Conditional
│
├─ 类>500行? ──────────→ Extract Class
│
├─ 参数>5个? ──────────→ Introduce Parameter Object
│
├─ 改一处要改多处? ────→ Move Method/Field(集中逻辑)
│
├─ 一个类响应多种变化? → Extract Class(分离变化轴)
│
├─ 函数总访问别的类? ──→ Move Method
│
└─ 业务概念用String? ──→ Replace Data Value with Object---
9. Module Cycle(循环依赖)
来源:《架构整洁之道》辩论修订版 - 双方共识保留的核心规则
识别信号:
- 模块 A 依赖 B,B 又依赖 A(直接循环)
- A→B→C→A 的间接循环
- 无法单独编译/测试某个模块
- "改一个模块,另一个必须同时改"
为什么是问题:
- 模块无法独立测试(必须同时 mock 多侧)
- 无法独立部署/发布
- 架构腐化的强信号
- 重构成本指数级增长
检测工具:
# JavaScript/TypeScript
npx madge --circular src/
# Java
jdeps -R -summary target/classes | grep cycle
# Go
go mod graph | tsort 2>&1 | grep -i cycle
# Python
pydeps --show-cycles src/重构手法: 1. 依赖倒置 → 提取接口到独立模块,双方都依赖接口 2. 回调/事件 → A 调用 B 时,B 通过回调/事件通知 A,而非直接依赖 3. 提取公共模块 → 把 A/B 共同依赖的部分提取为 C
代码示例:
# 坏味道:循环依赖
# order.py
from payment import PaymentService # Order → Payment
# payment.py
from order import Order # Payment → Order(循环!)
# 重构后:依赖倒置
# interfaces.py(独立模块)
class OrderInterface(ABC):
@abstractmethod
def get_total(self) -> Money: pass
# order.py
class Order(OrderInterface): # 实现接口
pass
# payment.py
from interfaces import OrderInterface # 只依赖接口
class PaymentService:
def charge(self, order: OrderInterface): passCI 集成建议:
# .github/workflows/ci.yml
- name: Check circular dependencies
run: |
npx madge --circular src/ && echo "No cycles found" || exit 1---
参考资料
- 《重构:改善既有代码的设计》(第2版) - Martin Fowler
- 《架构整洁之道》- Robert C. Martin(第14章 组件耦合)
- dev-playbooks 辩论修订版评估报告
- devbooks-reviewer 检查清单
---
10. 代码卫生检查
禁止提交的模式(阻塞级)
| 模式 | 检测命令 | 原因 |
|---|---|---|
test.only / describe.only | rg '\.only\s*\(' tests/ | 跳过其他测试 |
console.log / console.debug | `rg 'console\.(log\ | debug)' src/` |
debugger | rg 'debugger' src/ | 断点残留 |
@ts-ignore | rg '@ts-ignore' src/ | 隐藏类型错误 |
as any | rg 'as any' src/ | 类型安全绕过 |
TODO 无 issue | rg 'TODO(?!.*#\d+)' src/ | 无法追踪的待办 |
资源管理检查(警告级)
| 模式 | 检测方法 | 正确做法 |
|---|---|---|
| 非 readonly DisposableStore | rg 'private\s+(?!readonly)\s*_?\w*[Dd]isposable' | 使用 readonly |
| dispose() 未调用 super | 人工检查 override dispose | 必须调用 super.dispose() |
| setInterval 无清理 | 搜索 setInterval 无对应 clearInterval | 在 dispose 中清理 |
| 事件监听无移除 | 搜索 addEventListener 无 removeEventListener | 使用 AbortController |
分层约束检查
# 检查禁止的跨层依赖
# base 层不能依赖 platform/editor/workbench
rg "from ['\"](vs/(platform|editor|workbench))" src/vs/base/
# platform 层不能依赖 editor/workbench
rg "from ['\"](vs/(editor|workbench))" src/vs/platform/
# common 层不能依赖 browser/node
rg "from ['\"].*(browser|node)" src/**/common/类型安全检查
// 禁止:空对象断言
const config = {} as Config; // ❌
// 禁止:非空断言
const name = user!.name; // ❌
// 禁止:any 类型
function process(data: any) { } // ❌
// 正确:使用 unknown 或具体类型
function process(data: unknown) { } // ✓自动化检查脚本
#!/bin/bash
# hygiene-check.sh
set -e
echo "=== 代码卫生检查 ==="
# 1. 调试代码
if rg -l 'console\.(log|debug)|debugger' src/ --type ts 2>/dev/null; then
echo "❌ 发现调试代码"
exit 1
fi
# 2. test.only
if rg -l '\.only\s*\(' tests/ --type ts 2>/dev/null; then
echo "❌ 发现 test.only"
exit 1
fi
# 3. @ts-ignore
count=$(rg -c '@ts-ignore' src/ --type ts 2>/dev/null | wc -l)
if [ "$count" -gt 0 ]; then
echo "⚠️ 发现 $count 处 @ts-ignore"
fi
# 4. any 类型
count=$(rg -c ': any[^a-z]' src/ --type ts 2>/dev/null | wc -l)
if [ "$count" -gt 0 ]; then
echo "⚠️ 发现 $count 处 any 类型"
fi
echo "✅ 卫生检查通过"资源管理审查清单
本文档定义了资源管理的审查要点。
---
1) 资源泄漏常见模式
订阅/监听器泄漏
// 违规:订阅未取消
class MyComponent {
private handler = (e: Event) => { /* ... */ };
initialize() {
document.addEventListener('click', this.handler);
// 缺少对应的 removeEventListener
}
}
// 正确:使用 AbortController
class MyComponent {
private abortController = new AbortController();
initialize() {
document.addEventListener('click', this.handler, {
signal: this.abortController.signal
});
}
dispose() {
this.abortController.abort();
}
}定时器泄漏
// 违规:定时器未清理
class Poller {
start() {
setInterval(() => this.poll(), 1000);
// 缺少清理逻辑
}
}
// 正确:保存引用并在 dispose 中清理
class Poller {
private intervalId?: NodeJS.Timeout;
start() {
this.intervalId = setInterval(() => this.poll(), 1000);
}
dispose() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
}
}流/连接泄漏
// 违规:流未关闭
async function readFile(path: string) {
const stream = fs.createReadStream(path);
const data = await streamToString(stream);
// 如果 streamToString 抛出异常,流不会关闭
return data;
}
// 正确:使用 try-finally
async function readFile(path: string) {
const stream = fs.createReadStream(path);
try {
return await streamToString(stream);
} finally {
stream.destroy();
}
}
// 更好:使用 using(TypeScript 5.2+)
async function readFile(path: string) {
using stream = fs.createReadStream(path);
return await streamToString(stream);
}---
2) DisposableStore 模式
基本用法
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
class MyService extends Disposable {
// 必须是 readonly
private readonly _disposables = new DisposableStore();
constructor() {
super();
// 注册订阅到 store
this._disposables.add(
eventEmitter.on('change', () => this.handleChange())
);
// 注册定时器到 store
this._disposables.add(
new IntervalTimer(() => this.poll(), 1000)
);
}
override dispose() {
this._disposables.dispose();
super.dispose(); // 必须调用
}
}检查规则
| 规则 | 检测模式 | 严重程度 |
|---|---|---|
| DisposableStore 必须 readonly | private\s+(?!readonly)\s*_?\w*[Dd]isposable | Error |
| dispose() 必须调用 super | override\s+dispose\(\).*\{(?![\s\S]*super\.dispose) | Error |
| 订阅必须注册到 store | .on\(.*\) 后无 .add( | Warning |
| 测试必须检查泄漏 | 测试文件无 ensureNoDisposablesAreLeakedInTestSuite | Warning |
---
3) 测试中的资源泄漏检测
使用 ensureNoDisposablesAreLeakedInTestSuite
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
suite('MyService', () => {
// 在 suite 开始时启用泄漏检测
ensureNoDisposablesAreLeakedInTestSuite();
let service: MyService;
setup(() => {
service = new MyService();
});
teardown(() => {
service.dispose(); // 必须清理
});
test('should do something', () => {
// 测试代码
});
});常见测试泄漏
// 违规:测试创建的资源未清理
test('creates disposable', () => {
const disposable = new MyDisposable();
// 测试结束,disposable 未清理 → 泄漏
});
// 正确:使用 teardown 清理
let disposable: MyDisposable;
setup(() => {
disposable = new MyDisposable();
});
teardown(() => {
disposable.dispose();
});---
4) 审查检查清单
代码审查时必须检查
- [ ] DisposableStore 声明
- 是否使用
readonly修饰? - 是否使用
private?
- [ ] dispose() 方法
- 是否调用
super.dispose()? - 是否清理所有已知资源?
- 是否在基类中定义?
- [ ] 订阅/监听器
- 是否注册到 DisposableStore?
- 是否有对应的取消逻辑?
- 是否使用 AbortController?
- [ ] 定时器
- setInterval 是否有对应的 clearInterval?
- setTimeout 在组件销毁时是否取消?
- [ ] 流/连接
- 是否在 finally 块中关闭?
- 是否使用 using 语法?
- [ ] 测试
- 是否有
ensureNoDisposablesAreLeakedInTestSuite()? - teardown 是否清理所有创建的资源?
---
5) 自动化检测
ESLint 规则配置
// eslint.config.js
module.exports = {
rules: {
// 自定义规则:DisposableStore 必须 readonly
'local/code-no-potentially-unsafe-disposables': 'error',
// 自定义规则:dispose 必须调用 super
'local/code-must-use-super-dispose': 'error',
}
};grep 检测命令
# 检测非 readonly 的 DisposableStore
rg 'private\s+(?!readonly)\s*_?\w*[Dd]isposable' --type ts
# 检测 dispose 方法未调用 super.dispose
rg -U 'override\s+dispose\(\).*?\{[^}]*\}' --type ts | grep -v 'super.dispose'
# 检测未清理的 setInterval
rg 'setInterval\(' --type ts -l | xargs -I {} sh -c 'rg -c "clearInterval" {} || echo "Missing clearInterval: {}"'---
6) 修复指南
添加 DisposableStore
// Before
class MyClass {
private subscription: Subscription;
constructor() {
this.subscription = eventEmitter.on('change', () => {});
}
}
// After
class MyClass extends Disposable {
private readonly _disposables = new DisposableStore();
constructor() {
super();
this._disposables.add(
eventEmitter.on('change', () => {})
);
}
override dispose() {
this._disposables.dispose();
super.dispose();
}
}添加测试泄漏检测
// Before
suite('MyTest', () => {
test('does something', () => {
const obj = new MyDisposable();
// 未清理
});
});
// After
suite('MyTest', () => {
ensureNoDisposablesAreLeakedInTestSuite();
const disposables = new DisposableStore();
teardown(() => {
disposables.clear();
});
test('does something', () => {
const obj = disposables.add(new MyDisposable());
// 会在 teardown 中自动清理
});
});