10 - 可靠性保障体系设计
1. 问题定义
随着项目功能增长,AI 辅助开发面临一个特有的矛盾:
代码生成速度 ████████████████████ 快
人的审查速度 ████ 慢
代码理解深度 ██████ 随规模递减
改动的信心 ████ 随规模递减结果:项目中存在越来越多"AI 写的、你看过但没完全吃透"的代码。每次改动都有踩雷焦虑。
传统开发也有这个问题,但 AI 开发放大了它。 因为代码增长更快,而人的理解消化速度没有变。
2. 核心策略:缩小爆炸半径
所有可靠性手段归结为一个思想:让任何一次改动只影响尽可能小的范围,并且这个范围是可验证的。
改动前 → 知道会影响什么 (影响分析)
改动中 → 每一步都可验证 (测试纪律)
改动后 → 确认没有破坏其他东西 (回归验证)
线上 → 出问题能立刻发现和回滚(监控兜底)3. 改动前:影响分析
这是 AI 开发中最容易跳过的一步。AI 倾向于"接到任务就动手",但正确的做法是先搞清楚爆炸半径。
/impact Skill
| 维度 | 说明 |
|---|---|
| 触发时机 | 修改现有功能、重构、升级依赖前 |
| 分析范围 | 直接影响 → 间接影响 → 测试影响 → 风险评估 |
| 产物 | 影响范围清单(按文件 + 风险等级)、建议的改动顺序 |
完整提示词见
.claude/skills/impact/SKILL.md
何时使用 /impact
必须用 /impact 的场景:
- 修改共享类型/接口定义
- 修改 Service 层的函数签名
- 修改数据库 Schema
- 升级核心依赖的大版本
- 重构跨多个文件的逻辑
不需要 /impact 的场景:
- 修改单个组件的 UI
- 修复局部 Bug(影响范围明确)
- 新增独立功能(不改现有代码)4. 改动中:测试纪律
4.1 测试金字塔与五层分级
/ E2E(3-5 个) \ 核心用户路径
/ 集成测试(每个 API)\ API → Service → DB
/ 单元测试(每个 Service)\ 函数级逻辑验证
────────────────────────────具体的量化标准(务实,不追求 100%):
| 层级 | 覆盖要求 | 测试什么 | 不测什么 |
|---|---|---|---|
| 单元测试 | 每个 Service 函数 | 输入输出、边界条件、错误路径 | UI 组件的渲染细节 |
| 集成测试 | 每个 API Route | 完整请求→响应链路 | 第三方 API 的内部逻辑 |
| E2E 测试 | 3-5 个核心流程 | 用户注册→登录→核心操作 | 所有边界组合 |
测试分级(L0-L4)
| 级别 | 名称 | 范围 | 运行时机 | 目标耗时 |
|---|---|---|---|---|
| L0 | 冒烟测试 | 核心路径(登录、主流程) | 每次提交 | < 30s |
| L1 | 单元测试 | Service 函数、工具函数 | 每次提交 | < 2min |
| L2 | 集成测试 | API Route 端到端 | PR / 合并前 | < 5min |
| L3 | E2E 测试 | 用户关键路径 | 日构建 / 发布前 | < 15min |
| L4 | 性能/压力测试 | 响应时间、并发 | 发布前 / 定期 | 按需 |
个人项目建议至少覆盖 L0-L2,L3 按项目规模按需补充。
4.2 AI 开发中的测试规则
写入 CLAUDE.md 的规则:
## 测试纪律
- MUST 新功能先跑现有测试全绿,再开始写代码
- MUST 每个 Service 函数有对应的单元测试
- MUST 每个 API Route 有对应的集成测试
- MUST 修复 Bug 时先写一个能复现 Bug 的测试,再修复
- MUST 改动完成后跑全量测试,全绿才能提交
- NEVER 因为赶进度跳过测试
- NEVER 删除或注释掉失败的测试(修代码,不修测试)AI 生成代码验收标准
AI 生成的代码在合并前必须满足:
- 测试覆盖 — 新增代码有对应的 L1 单元测试
- 类型完整 —
npx tsc --noEmit零错误 - 人工阅读 — 至少阅读过 diff,理解核心逻辑
- 边界检查 — AI 容易忽略的边界条件(空值、超长输入、并发)已有测试
PR 体积控制
单次 PR 改动不超过 300 行(不含测试代码和自动生成文件)。超出时:
- 拆分为多个 PR,每个有独立的功能意义
- 先合并基础设施(类型、迁移),再合并业务逻辑
4.3 /test Skill(增强版)
| 维度 | 说明 |
|---|---|
| 触发时机 | 新功能完成后、Bug 修复时 |
| 生成范围 | 正常路径 + 边界条件 + 错误路径 + 回归测试(Bug 修复时) |
| 核心原则 | 基于接口合同写测试,不基于内部实现;Bug 修复必须有回归测试 |
| 产物 | 测试文件 + 执行结果报告 |
完整提示词见
.claude/skills/test/SKILL.md
4.4 Flaky 测试处置流程
偶发性失败的测试(flaky test)是测试可信度的最大杀手。
严重程度分级
| 级别 | 定义 | 处置 |
|---|---|---|
| P1 | 阻塞 CI 通过,影响所有提交 | 立即修复或临时 skip + 创建 issue |
| P2 | 偶尔失败(< 10%),重跑可过 | 48 小时内修复,标记 @flaky |
| P3 | 极少失败(< 1%),难以复现 | 收集日志,一周内排查 |
排障步骤
- 复现:隔离运行失败的测试
npm test -- --grep "test name" - 检查常见原因:
- 依赖外部服务(网络、数据库状态)
- 时间依赖(setTimeout、Date.now)
- 测试间状态泄漏(共享变量、未清理的数据)
- 异步竞态(缺少 await、事件顺序依赖)
- 修复后连续运行 5 次确认稳定
反模式
- 禁止:因为 flaky 就删除测试 — 修测试,不删测试
- 禁止:在 CI 中无限重试 — 掩盖问题不是解决问题
- 禁止:
skip后不跟 issue — 跳过必须有跟踪
4.5 小步验证循环
每个功能实现时的节奏:
改一个函数 → 跑测试 → 绿 → 继续
→ 红 → 立即停下,修复后再继续
改完一个模块 → 跑全量测试 → 绿 → 提交
→ 红 → 定位问题,不要累积CLAUDE.md 中的规则:
- NEVER 连续修改多个文件而不跑测试。每完成一个逻辑单元就验证。
- 如果测试失败,立即停下来告诉我,不要自行猜测修复。5. 改动后:回归验证
5.1 提交前全量验证
在 /commit Skill 中增加前置检查:
提交前必须验证:
1. npm run lint — 零警告
2. npx tsc --noEmit — 零类型错误
3. npm run test — 全部通过
4. git diff --check — 无空白错误
任何一项不通过,不允许提交。5.2 CI 门禁建议
个人项目使用 GitHub Actions 的最小 CI 配置:
# .github/workflows/ci.yml 核心步骤
- npm ci
- npm run lint # L0: 格式检查
- npx tsc --noEmit # L0: 类型检查
- npm run test # L1-L2: 单元+集成测试门禁规则:
- 合并条件:以上步骤全部通过
- 可选:L3 E2E 测试在日构建或发布前运行
- 覆盖率:建议设置底线(如 60%),但不追求 100%
详见 18-Git 工作流 §9 提交前检查清单
5.3 Stop Hook — AI 完成任务后自动验证
# .claude/hooks/stop-verify.sh
#!/usr/bin/env bash
set -euo pipefail
# AI 完成一轮回复后,提醒验证状态
echo '{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "任务完成检查:1. 是否运行了相关测试?2. 测试是否全绿?3. 是否有未保存的变更?如果没有运行测试,现在运行。"
}
}'
exit 0settings.json 追加:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": ".claude/hooks/stop-verify.sh"
}]
}]
}
}5.3 回归测试纪律
每个 Bug 修复必须留下一个回归测试。这是整个可靠性体系中最重要的单条规则。
原因:没有回归测试的 Bug 修复,三个月后重构时一定会复发。AI 不记得三个月前的修复细节,但测试记得。
Bug 报告
→ /debug 定位根因
→ 先写一个能复现 Bug 的测试(此时测试应该 FAIL)
→ 修复代码(测试变 PASS)
→ 这个测试永远留在测试集里6. 线上:监控兜底
测试覆盖已知场景,监控覆盖未知场景。
6.1 最小监控方案
个人项目不需要复杂的监控体系,但需要:
必须有:
[1] 错误追踪 — Sentry(免费 tier 够用)
捕获线上未处理异常,带堆栈和上下文
[2] 健康检查端点 — /api/health
返回服务状态、数据库连通性、版本号
推荐有:
[3] 关键指标日志
API 响应时间、错误率、数据库查询耗时6.2 /health API 模板
// app/api/health/route.ts
import { prisma } from '@/lib/prisma'
export async function GET() {
const checks = {
status: 'ok',
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || 'dev',
checks: {} as Record<string, 'ok' | 'error'>
}
// 数据库连通性
try {
await prisma.$queryRaw`SELECT 1`
checks.checks.database = 'ok'
} catch {
checks.checks.database = 'error'
checks.status = 'degraded'
}
const statusCode = checks.status === 'ok' ? 200 : 503
return Response.json(checks, { status: statusCode })
}6.3 Sentry 集成
// lib/sentry.ts — 错误追踪初始化
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1, // 10% 采样
environment: process.env.NODE_ENV,
})7. 架构层面的可靠性设计
7.1 模块边界的强制执行
写入 CLAUDE.md:
## 模块边界
- 组件层(/components)不直接调用数据库
- API Route 只负责请求解析和响应格式化,业务逻辑在 Service 层
- Service 层是业务逻辑的唯一归属地
- 跨模块通信通过 TypeScript 接口(types/ 目录),不直接引用内部实现
调用方向(单向,不可反转):
组件 → hooks → API → services → prisma
禁止:
- services 直接引用 components
- prisma 操作出现在 components 或 API Route 中
- 跨 Service 的直接函数调用(通过接口)7.2 类型契约作为安全网
TypeScript 的类型系统是免费的"接口测试":
// types/api.ts — 共享类型契约
export type ApiResponse<T> = {
data: T | null
error: { code: string; message: string } | null
meta?: { page: number; total: number }
}
// types/user.ts — 模块间传递的数据结构
export type UserDTO = {
id: string
email: string
name: string
role: 'admin' | 'user'
}类型变了 → TypeScript 编译器自动报告所有受影响的地方 → 零成本的影响分析。
7.3 数据库迁移纪律
## 数据库变更规则
- NEVER 手动修改数据库,所有变更通过 Prisma migration
- MUST 先在本地验证 migration,再推到生产
- MUST 数据迁移(migration)和代码部署分开执行
- 破坏性变更(删列、改类型)必须分两步:
1. 先部署新代码兼容新旧两种 schema
2. 再执行 migration
3. 最后清理旧兼容代码8. 信心度量
怎么知道项目的可靠性在变好还是变差?
简单的健康指标
不需要复杂仪表板,定期问自己三个问题:
1. 我敢不敢不看代码就合并 AI 的 PR?
→ 如果不敢,说明测试覆盖不够
2. 上次线上出问题是什么时候?多久发现的?
→ 如果发现时间 > 1 小时,说明监控不够
3. 我最怕改哪个模块?
→ 那个模块就是当前的最大风险点,优先补测试审查者自身的可靠性
代码可靠性依赖审查质量,而审查者也会犯错。高风险审查场景:
| 场景 | 风险 | 对策 |
|---|---|---|
| 连续审查超过 45 分钟 | 注意力下降,漏审关键逻辑 | 强制休息,拆分会话 |
| 审查不熟悉的领域 | 无法识别领域特有的风险 | 先做探索会话理解上下文再审查 |
| 赶工期时审查 | 倾向于"看起来对就过" | 至少确保安全和数据操作的审查不跳过 |
| AI 输出"看起来很完美" | 放松警惕(确认偏差) | 越完美越要抽查细节,尤其是边界条件 |
核心认识:测试覆盖是对审查者精力不足的系统性补偿。 不是"有了测试就不用审查",而是"测试保护你在疲劳时漏审的部分"。
/health-report Skill
| 维度 | 说明 |
|---|---|
| 触发时机 | 定期使用(建议每两周) |
| 检查范围 | 测试覆盖 → 类型安全 → 模块耦合 → 风险区域(变更热点) → 功能覆盖(联动 docs/features.md) |
| 产物 | 健康度评分 + 最大风险 + 优先改进建议(Top 3) |
完整提示词见
.claude/skills/health-report/SKILL.md
9. 完整的可靠性保障链
功能开发的完整流程:
需求来了
│
├── 新功能?→ /plan 出方案 → /features add 注册 → 审批
├── 改现有功能?→ /impact 影响分析 → 确认范围
├── 修 Bug?→ /debug 定位 → 先写回归测试
│
▼
开始写代码
│
├── 现有测试全绿?→ 是 → 继续
│ → 否 → 先修测试
├── 每改一个函数 → 跑测试 → 绿才继续
├── Hooks 自动格式化 + 行数检查 + 依赖拦截
│
▼
写完了
│
├── /test 生成测试(正常/边界/错误路径)
├── 全量测试 → 绿?
│ → 是 → /simplify 精简重构(在 commit 后运行,可回退)
│ → /review 代码审查(正确性/性能/规格)
│ → /security-review(安全敏感变更时)
│ → 否 → 修复后重跑
│
▼
准备提交
│
├── lint + tsc + test 三项全绿
├── /commit 提交
├── (可选)新会话审查 → 对抗性测试
│
▼
合并 & 部署
│
├── CI 再跑一遍全量测试
├── 部署后 /api/health 检查
├── Sentry 监控兜底
│
▼
持续运行
│
├── 每两周 /health-report 体检
├── 变更热点 = 下一个要补测试的模块
└── Bug 修复 → 必须有回归测试 → 测试集越来越厚10. 与其他子系统的关系
CLAUDE.md:写入测试纪律和模块边界规则
Skills:/impact, /test, /health-report 三个可靠性 Skill;/features 提供功能级测试覆盖视角
Hooks:Stop Hook 提醒验证、文件行数守卫
MCP:Playwright 做 E2E 测试
脚本 check:CI 中的自动化质量关卡
doc-18 Git 工作流:提交前检查清单、原子提交保障测试粒度