本页目录

软工 II · CI/CD 与代码质量

对标:Continuous Delivery(Humble–Farley)/ Google SWE at Google | 前置:se-01(Git、测试)、cloud 线(部署目标) 从"写好代码"到"可靠地、反复地交付代码"。这一页讲 CI/CD(持续集成/持续交付——把构建、测试、部署自动化成流水线,现代软件交付的标配)和代码质量的工程实践(审查、静态分析、技术债管理)。这些是让软件项目可持续的纪律——尤其对你的 Medusa 这种要长期演进 + 多方(你/GPT/OPS)协作的系统。

学习层:流水线的绿灯,能把哪个产物送到生产?

1. 具体谜题:一个 PR 到底卡在哪里?

有四个候选发布:一个单元测试失败,一个密钥扫描失败,一个 review 尚未批准,一个金丝雀错误率升高。它们的构建都生成了文件。先预测:

  1. 哪一个最早的红灯足以阻止合并?
  2. CI 通过后,为什么还不能把任意本地目录直接部署?
  3. 金丝雀阶段发现回归时,应优先扩大全量、回滚,还是继续让流量进入?

实验台固定 build → test → lint → security → review → canary → deploy 的门,并隐藏每个候选的第一失败点。提交预测后,再看 gate ledger、artifact provenance 和回滚路径。重点是理解门的组合语义,不是把 CI 当作一串漂亮的状态徽章。

2. 最小模型:失败即停止,产物带着提交身份前进

把每一关表示为谓词 g_i(commit, artifact, evidence)。一个候选能合并的条件是

\[ G=\bigwedge_i g_i=\mathrm{true}, \]

部署的对象不是“当前工作目录”,而是由某个已验证 commit 产生、可追溯、可复现的 artifact。金丝雀再增加运行时观测谓词 health(artifact, traffic_slice);它通过不代表全量用户永远无故障。

3. 正式机制与不变量:CI 是门,CD 是受控传播

  • fail-closed:构建、测试、静态检查、安全扫描和必要审批任一失败,默认不合并、不发布;手工 override 必须留下理由、身份和审计记录。
  • 来源不变量:生产 artifact 的 commit、依赖锁文件、构建日志和签名/摘要可回溯;不能用另一份“本地等价文件”替换它。
  • 门的分工:单测抓局部行为,集成测接缝,静态分析做保守筛选,密钥/依赖扫描检查供应链风险,canary 检查运行时信号。它们互补,不是重复投票。
  • 恢复不变量:每次渐进发布都有可执行的回滚版本;监控信号的时间窗口、阈值和错误预算要先定义。

因此“全部绿色”只意味着声明的谓词在声明的环境中通过。它不能证明没有未知 bug,也不能替代生产可观测性和事故响应。

4. 失败边界与迁移任务

测试 flaky、扫描误报、环境漂移、未覆盖的迁移脚本、错误的健康检查和过宽的 override 都会让门失真;持续交付也不等于持续部署。实验使用固定候选和离散阶段,不模拟真实 GitHub Actions 的并发、缓存、权限和秘密注入。

迁移任务:给 Medusa 的 predict.py 设计最小 CI gate contract:依赖安装、pytest、静态检查、密钥扫描、artifact 摘要、人工批准和回滚版本各自留下什么证据。对一次故障写清“哪一关本应挡住它、为什么没挡住、如何补门”,不要只增加一个泛化的“再跑一次测试”。

JavaScript 失效时的静态读法:按流水线从左到右找第一处失败;第一处红灯就足以阻止后续传播。artifact 必须绑定已验证 commit,canary 回归应触发回滚。

候选 第一失败门 能合并 能部署 证据结论
clean-release 无 是 是 artifact 与 commit 可追溯
unit-regression test 否 否 先修局部行为
secret-found security 否 否 轮换秘密并清理历史
canary-regression canary 是 否 回滚,不扩大全量流量

1. CI:持续集成——让"合并"不再可怕

问题:多人(或你 + GPT)各自改代码,攒很久才合并 → 冲突地狱 + "在我机器上是好的"。持续集成(CI):频繁地把改动合并到主干,每次合并自动运行构建 + 测试——尽早发现集成问题。

CI 流水线(每次 push / PR 触发,GitHub Actions 等跑):

  1. 拉代码 → 构建(编译/打包,能构建吗?)。
  2. 跑测试(se-01 的单元 + 集成,全过吗?)。
  3. 静态检查(lint、类型检查、格式)。
  4. 失败就挡住合并——红了不许并进主干。

核心价值:主干始终处于"可构建、测试通过"的健康状态——任何人任何时候拉主干都能跑。"每次改动都自动验证"把'集成'从偶尔的大灾难变成持续的小事(🔗 memory 里"dev 声明完成后 arch 必 git verify"——CI 就是把这个 verify 自动化)。对你和 GPT 协作:CI 是自动的验收关卡,GPT 提交的代码先过 CI 再说。

2. CD:持续交付/部署——让"上线"变常规

持续交付(Continuous Delivery):CI 通过后,自动把产物准备到"随时可一键部署"的状态。持续部署(Continuous Deployment):更进一步,通过所有检查就自动上生产。

为什么要自动化部署:手动部署(memory 里 Medusa 的手动 schtasks、手动点开 webapp)容易出错、不可复现、依赖某个人。自动化部署:

对 Medusa 的现实:它现在是手动运维(memory:webapp 挂了手动拉起、无主动告警)。本页不是要你立刻上 K8s——而是让你知道"手动运维的痛(静默失败、不可复现、依赖人)正是 CI/CD 要解决的"。哪怕先做一个"push 到某分支就自动跑测试 + 部署到 Win"的最小流水线,就消除了大量手动出错空间。

3. 代码质量:让代码能被人(和未来的你)读懂

代码写一次、读多次——可读性 > 小聪明。工程实践:

4. 技术债:工程的核心权衡

技术债:短期加速借款、长期付利息的曲线/隐喻。

图 se-02.2技术债:短期加速借款、长期付利息的曲线/隐喻。

技术债:为了快速交付而做的"次优但能用"的选择——像借钱,短期加速、长期付利息(越来越难改)。关键不是"消灭技术债"(不可能也不必要),而是有意识地管理它:

方法论:软件工程的成熟度,很大程度是"管理不完美"的能力——完美的代码、零债、100% 测试都不现实。知道在哪投入质量、在哪接受够用、何时还债——这种工程判断力比任何单一技术都值钱(呼应 perf 的"优化该优化的"、系统设计的"够用即可")。

5. 把全线串起来:一个改动的完整旅程

CI/CD 流水线:push→构建→测试→静态检查→审查→部署(金丝雀→全量)→监控→回滚。

图 se-02.1CI/CD 流水线:push→构建→测试→静态检查→审查→部署(金丝雀→全量)→监控→回滚。

综合 se-01/02,一个功能改动的现代工程旅程:

  1. 从主干拉 feature 分支(se-01 git)。
  2. 写代码 + 写测试(TDD/测试金字塔 se-01)。
  3. 本地跑测试 + lint 通过。
  4. push → 开 PR → CI 自动构建 + 测试 + 静态检查(本页)。
  5. 代码审查(人/AI)→ 修改 → 批准。
  6. 合并主干 → CD 自动部署(金丝雀 → 全量)。
  7. 监控(cloud-02 可观测性)→ 有问题快速回滚。

这条流水线就是现代软件团队的"生产线"——它让"频繁、可靠、低风险地交付"成为可能。你的 Medusa 越往这条线靠,运维就越省心、越不依赖"某个人记得怎么操作"。

6. 练习与要点

例 1(设计最小 CI) 为 Medusa 的某个组件(如 predict.py)写一个 GitHub Actions:push 时装依赖 + 跑 pytest——把"手动 verify"自动化成关卡。

例 2(识别技术债) 在 Medusa 里找一处"能用但每次改都痛"的地方,判断是有意债还是无意债、该不该现在还——练技术债的管理判断。

例 3(回滚 vs 修复) 生产出了 bug,是"赶紧回滚上一版"还是"热修复"?——权衡影响面、修复信心、回滚成本。理解"快速回滚能力"为什么是部署的核心指标(memory 里"OPS 操作前习惯性备份"正是回滚思想)。\(\blacksquare\)


下一页:云 I——容器与 Docker 原理:你的 medusa-postgres 跑在 Docker 里,但容器底下是什么?Linux 内核的隔离原语如何造出"轻量虚拟机"的幻觉。