Skip to content

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
L3E2E 测试用户关键路径日构建 / 发布前< 15min
L4性能/压力测试响应时间、并发发布前 / 定期按需

个人项目建议至少覆盖 L0-L2,L3 按项目规模按需补充。

4.2 AI 开发中的测试规则

写入 CLAUDE.md 的规则:

markdown
## 测试纪律

- MUST 新功能先跑现有测试全绿,再开始写代码
- MUST 每个 Service 函数有对应的单元测试
- MUST 每个 API Route 有对应的集成测试
- MUST 修复 Bug 时先写一个能复现 Bug 的测试,再修复
- MUST 改动完成后跑全量测试,全绿才能提交
- NEVER 因为赶进度跳过测试
- NEVER 删除或注释掉失败的测试(修代码,不修测试)

AI 生成代码验收标准

AI 生成的代码在合并前必须满足:

  1. 测试覆盖 — 新增代码有对应的 L1 单元测试
  2. 类型完整npx tsc --noEmit 零错误
  3. 人工阅读 — 至少阅读过 diff,理解核心逻辑
  4. 边界检查 — 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%),难以复现收集日志,一周内排查

排障步骤

  1. 复现:隔离运行失败的测试 npm test -- --grep "test name"
  2. 检查常见原因:
    • 依赖外部服务(网络、数据库状态)
    • 时间依赖(setTimeout、Date.now)
    • 测试间状态泄漏(共享变量、未清理的数据)
    • 异步竞态(缺少 await、事件顺序依赖)
  3. 修复后连续运行 5 次确认稳定

反模式

  • 禁止:因为 flaky 就删除测试 — 修测试,不删测试
  • 禁止:在 CI 中无限重试 — 掩盖问题不是解决问题
  • 禁止skip 后不跟 issue — 跳过必须有跟踪

4.5 小步验证循环

每个功能实现时的节奏:

改一个函数 → 跑测试 → 绿 → 继续
                    → 红 → 立即停下,修复后再继续
改完一个模块 → 跑全量测试 → 绿 → 提交
                          → 红 → 定位问题,不要累积

CLAUDE.md 中的规则:

markdown
- 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 配置:

yaml
# .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 完成任务后自动验证

bash
# .claude/hooks/stop-verify.sh
#!/usr/bin/env bash
set -euo pipefail

# AI 完成一轮回复后,提醒验证状态
echo '{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "任务完成检查:1. 是否运行了相关测试?2. 测试是否全绿?3. 是否有未保存的变更?如果没有运行测试,现在运行。"
  }
}'
exit 0

settings.json 追加:

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 模板

typescript
// 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 集成

typescript
// 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:

markdown
## 模块边界

- 组件层(/components)不直接调用数据库
- API Route 只负责请求解析和响应格式化,业务逻辑在 Service 层
- Service 层是业务逻辑的唯一归属地
- 跨模块通信通过 TypeScript 接口(types/ 目录),不直接引用内部实现

调用方向(单向,不可反转):
  组件 → hooks → API → services → prisma

禁止:
  - services 直接引用 components
  - prisma 操作出现在 components 或 API Route 中
  - 跨 Service 的直接函数调用(通过接口)

7.2 类型契约作为安全网

TypeScript 的类型系统是免费的"接口测试":

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 数据库迁移纪律

markdown
## 数据库变更规则

- 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 工作流:提交前检查清单、原子提交保障测试粒度

面向个人开发者的 AI 辅助编程工程化方案