管道设计
将 CI 管道设计为一系列快独立门控、从便宜到贵渐进抓问题。
为什么
CI 管道是一系列质量门控——每门控抓一类问题(lint 抓样式、类型检查抓类型错、单元测试抓逻辑 bug、e2e 抓集成 bug)问题存在时阻构建。管道设计定反馈速度(快门控给快反馈、慢门控阻开发者)和缺陷漏(缺或太弱门控让 bug 到生产)。关键原则是渐进过滤: 先跑便宜快门控(lint 10 秒)后跑贵慢门控(e2e 10 分钟)、大多失败快抓、仅少数过所有 便宜门控的 PR 付贵门控代价。并行跑所有门控等所有完的管道比先跑便宜门控失败短路的慢。无门控管道让每 bug 过;太多门控管道慢开发者绕。艺术是找对门控集——足够抓重要 bug、快到不阻开发者。
何时使用
项目开始和反馈环感太慢或太多 bug 到生产时——管道设计是两者杠杆。
如何做
1. 识你需的门控 - Lint(样式、常见 bug): 10-30 秒 - 类型检查(类型错): 30-60 秒 - 单元测试(逻辑): 1-5 分钟 - 集成测试(API、DB): 5-10 分钟 - E2E 测试(用户流): 10-30 分钟 - 安全扫描(CVE、SAST): 2-5 分钟 - 构建(编译、打包): 1-5 分钟 2. 从便宜到贵排门控 - 先 lint(10 秒)——失败则停(坏代码无点跑测试) - 后类型检查(30 秒)——失败则停 - 后单元测试(2 分)——失败则停 - 后构建(3 分)——失败则停 - 后集成测试(5 分)——失败则停 - 后 e2e(15 分)——仅以上都过才跑 - 这首次失败短路、省时 3. 并行跑独立门控 - Lint 和类型检查可并行(都快、独立) - 单元测试和安全扫描可并行 - 但单元测试过前不跑 e2e(e2e 贵) - 用矩阵构建并行作业 4. 激进缓存 - 依赖: 缓存 node_modules、~/.m2、~/.cargo - 构建产物: 缓存 dist/、target/、build/ - 测试结果: 缓存测试结果跳未改测试 - 这减门控时间从分钟到秒 5. 使门控强制 - 分支保护: 合前要求所有门控过 - 不允许绕(即使管理员) - 可绕门控是被绕门控 6. 监控调优 - 追管道时间(中位和 90 百分位) - 管道慢识最慢门控优化 - bug 漏识缺门控加 - 管道非设后忘;随项目演进
示例
# .github/workflows/ci.yml — 渐进门控
name: CI
on: [pull_request]
jobs:
# 门控 1: Lint(10 秒)——最便宜、先跑
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx eslint . --max-warnings 0
- run: npx prettier --check .
# 门控 2: 类型检查(30 秒)——与 lint 并行
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx tsc --noEmit
# 门控 3: 单元测试(2 分)——lint 和 typecheck 过后
unit-test:
needs: [lint, typecheck] # 等便宜门控
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage --maxWorkers=4
# 门控 4: 构建(3 分)——单元测试过后
build:
needs: [unit-test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
# 门控 5: E2E 测试(15 分)——贵、仅以上都过才跑
e2e-test:
needs: [build] # 等构建
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- run: npx playwright test
# 分支保护: 要求所有门控过
# 设置 > 分支 > 分支保护规则:
# - 要求状态检查: lint、typecheck、unit-test、build、e2e-test
# - 合前要求分支最新
# - 不允许绕(即使管理员)
# 缓存: 加速门控
# .github/workflows/ci.yml(每作业加):
# - uses: actions/cache@v4
# with:
# path: node_modules
# key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }}常见问题
- ⚠并行跑所有门控等所有完——lint 失败 10 秒抓但管道等 15 分钟 e2e 完才报失败、开发者阻 15 分钟于 10 秒问题。
- ⚠无论改什么每 PR 跑 e2e——仅 CSS 改触 15 分钟 e2e、开发者等 15 分钟不可能失败的门控、管道感慢。
- ⚠允许管理员绕门控——资深开发者推类型检查不过代码、团队学门控可选、门控变戏非真保护。
- ⚠完全无门控——每 PR 直合 main、bug 发到生产、团队靠热修非预防、比 CI 门控贵 10 倍。
最佳实践
- ✓从便宜到贵排门控(lint 10 秒 → 类型检查 30 秒 → 单元测试 2 分 → 构建 3 分 → e2e 15 分)首次失败短路——这给大多失败快反馈、仅过便宜门控的 PR 付贵门控。
- ✓并行跑独立门控(lint 和类型检查)但用 needs: 等便宜门控后跑贵——并行加速管道但渐进过滤失败短路。
- ✓激进缓存依赖、构建产物和测试结果——这减门控时间从分钟到秒、快管道是开发者不绕的管道。
- ✓分支保护使所有门控强制不允许绕(即使管理员)——可绕门控是被绕门控、团队学门控可选。