// WHY THIS MATTERS
两个现实同时砸过来。其一:你接手 / 维护一个几十万行、没测试、没规范、上一代人留下的 legacy 库,想引入新治理(类型、架构约束、测试门),但「一上来全库强制」会瞬间冒出几千个 violation——没人看,最后整套规则被关掉。其二:AI 让写 代码的吞吐暴涨,PR 数量翻几倍,而 review 还是人肉、还是一行行读 ,于是瓶颈从「写」整体平移到「审」;reviewer 累到 rubber-stamp,AI 生成代码里那些幻觉 API、偷改测试、漏掉的 edge case 就这么混进 main。这两件事其实是同一个工程问题 :如何在不靠「更多人力」的前提下,给一个混乱的大系统引入可持续的质量约束。本期四件落地工程:用 strangler / codemod 渐进引入、用棘轮(ratchet)让库只能变好、用护栏即代码把规则变成 CI 红绿、以及把 review 分层让人只看机器判不了的。核心心法:治理不是「写进 wiki 让大家注意」,是把约束变成 diff 上的自动门。
// 01
渐进引入:Strangler 与 codemod,别大爆炸
论断:在 legacy 库里「一次性全库上新规则」必然失败——几千告警没人看 = 规则被关。新治理只该作用于 新代码 + 本次改动的文件 。
背景与原理
Martin Fowler 的 Strangler Fig :不重写,而是在边界处长出新结构,逐步「绞杀」旧代码,直到旧的可以摘除。落到治理上有两条具体手法。其一,changed-files-only gating :新规则(类型检查、lint、复杂度上限)只对 PR 里实际改动的文件 强制,存量豁免但冻结——这样开发者「碰到哪改到哪」,治理随自然改动渗透,而非一夜之间引爆。其二,机械的存量改造交给 codemod (jscodeshift / OpenRewrite / ast-grep)——AST 级脚本批量改,而不是人逐文件手搓。AI 在这里的角色很明确:codemod 写不动的长尾(语义不规整、需要判断的)交给 agent 批量改,但每个改动仍走后面 §3/§4 的门。
实战示例
# 只对本次 PR 改动的文件跑严格检查,存量不动
CHANGED=$(git diff --name-only origin/main... | grep '\.ts$' )
if [ -n "$CHANGED" ]; then
npx tsc --noEmit --strict $CHANGED # 新/改的文件必须过 strict
npx eslint --max-warnings 0 $CHANGED # 存量旧文件不在列表里,豁免
fi
# 机械迁移用 codemod,不是手工:
# npx jscodeshift -t rename-api.js src/ · ast-grep --pattern '...' --rewrite '...'
核心 mental model:把治理接到 diff 上,而不是接到全库扫描上。 全库扫描产出几千个你今天不会修的 violation,只会训练团队「忽略红色」——这是治理之死。
失败模式 :(1)big-bang 重写 / 一次性全库强制——violation 海啸,团队学会无视 CI 红灯。(2)codemod 能做的机械变更让人手工改——慢且引入手误。(3)改了存量文件却没让它「升级」到新标准——碰过的文件应顺手纳入治理,否则永远绞杀不完。
// 02
棘轮与护栏即代码:让库只能变好
论断:治理的目标不是「立刻清零」,是单调改善 ——允许存量欠债,禁止新增欠债,且债只减不增。
背景与原理
Ratchet(棘轮) :记录当前 violation 数为 baseline,CI 只在「比 baseline 更差」时 fail。存量被容忍,新增被挡住;每修好一点就把 baseline 调低,且不可回升 ——像棘轮一样只朝一个方向转。工具如 betterer 把这做成了通用机制。配套的是护栏即代码 / fitness functions (《Building Evolutionary Architectures》):把架构约束写成可执行测试 ——依赖方向、分层边界、禁止 import、复杂度上限——用 ArchUnit / dependency-cruiser / import-linter 跑。治理从此从「文档 + 人盯」变成「CI 红绿」,不依赖谁记得。AI 的位置:让 agent 每个 PR 顺手降一点 baseline(修一两个存量),长尾债在日常改动中被慢慢磨平,而不是立一个永远没空做的「重构 sprint」。
实战示例
# fitness function:把「UI 层不许直接 import 数据库」写成测试
import { dependencyCruiser } from "dependency-cruiser"
forbidden = [{
name: "ui-not-to-db" ,
from: { path: "^src/ui" },
to: { path: "^src/db" }, # 违反 = CI 红,不靠 code review 抓
}]
# ratchet:只挡「比昨天更糟」
BASE=$(cat .debt-baseline) # 例如 412
NOW=$(count_violations)
if [ "$NOW" -gt "$BASE" ]; then echo "债增加了:$BASE→$NOW" ; exit 1; fi
[ "$NOW" -lt "$BASE" ] && echo "$NOW" > .debt-baseline # 变好就锁住新低点
失败模式 :(1)规则只写在 wiki / CONTRIBUTING,没进 CI——等于不存在,靠人脑执行的治理必然衰减。(2)ratchet 允许 baseline 回升——债又涨回来,棘轮失去意义。(3)fitness function 一次性设得太严,存量瞬间全红——回到 §1 的海啸,应配 ratchet 渐进收紧。
进阶资源 · Ford/Parsons/Kua
Building Evolutionary Architectures (fitness functions),
thoughtworks.com ·
betterer (通用 ratchet 工具),
betterer 文档
// 03
让 review 不成瓶颈(上):自动门先过滤
论断:AI 让「写」提速 5×,review 还是人肉逐行 → 瓶颈平移到审。解法不是招更多 reviewer,是让机器先过滤 ,人只看机器判不了的。
背景与原理
把 code review 想成 Day 34 HITL 的 confidence routing 落在代码上:每个 PR 先过一排 pre-human gate(机器门) ——测试、类型、lint、coverage delta、SAST/secret 扫描、依赖审计、变更影响分析。门没过 = deny,根本不进人的队列 ;门全过才轮到人决定要不要细审。这对 AI 生成代码尤其值钱:AI 最高频的失败——幻觉不存在的 API、把失败测试改成 assert True 让 CI 转绿(见 Day 31 的 test tampering)、漏掉 null / 并发 edge case——很大一部分是机器可检测的 。让 sanitizer、类型系统、mutation test、secret scanner 去抓这层,人的注意力才不会浪费在「机器本可以查出来的东西」上。关键原则反过来也成立:别让人去做机器更擅长的 (人眼读不出 race condition,但 ThreadSanitizer 能)。
PR ──▶ ┌─────────── PRE-HUMAN GATES(全自动) ───────────┐
│ tests · types · lint · coverageΔ · SAST │
│ secret-scan · dep-audit · 变更影响分析 │
└───────────────┬─────────────────────────────────┘
任一不过 ──▶ DENY:打回作者,不占人审 slot
全过 ──────▶ 进入 §4 风险分级
├─ 高风险 → 人 full review
├─ 低风险 → 抽样 + 事后审计
└─ 机械/低危 → auto-merge
机器判得了的别给人 · 人只看「机器过了但仍需判断」的
失败模式 :(1)把机器能查的留给人肉眼当防线——疲劳 + 漏检,双输。(2)没有 coverage / secret / SAST 门,指望 reviewer 一眼看出泄露的 key 或没覆盖的分支。(3)门太慢(30 分钟 CI)——开发者绕过、批量合,门形同虚设;门必须快到「过不了立刻知道」。
// 04
让 review 不成瓶颈(下):风险分级 · 抽样 · provenance
论断:过了自动门的代码,再按风险 决定人审强度——高风险全审、低风险抽样 + 事后审计。一刀切的「每个 PR 都细审」既慢又假。
背景与原理
三件事把人的注意力压到「能认真审」的量级。① 风险打分 :按改动碰到的路径定级——支付 / 认证 / 数据迁移 / 加密 = 高;文案 / 测试 / 注释 = 低——再叠加 diff 体量与 blast radius(见 Day 31)。高风险强制 full review + CODEOWNERS 指定 owner;低风险抽样人审 + 全量事后审计。② 信任分层 :给「AI 作者」与「人类作者」不同的默认 review 强度,并随 track record 调整(同 Day 34 的审计回归阈值)——新接入的 agent 严审,跑稳了再放宽。③ Provenance / attestation :标注哪些代码由 AI 生成、用了什么模型 / prompt(SLSA 式溯源)。出事时能精确定位、能批量召回「某个坏 prompt 产出的所有代码」——这是 AI 规模化后的必需品,不是合规摆设。
实战示例
def review_policy(pr):
paths = pr.changed_paths
high = any(p.startswith(("src/payment" ,"src/auth" ,"migrations/" )) for p in paths)
risk = "high" if high or pr.diff_lines > 400 else "low"
trust = author_trust(pr.author) # AI 新手 < 人类资深,随记录调整
if risk == "high" : return "full_review + CODEOWNERS"
if trust < 0.7: return "full_review"
return "sample_5pct + post_merge_audit" # 低风险高信任 → 抽样
# PR 元数据带上来源,出事可溯源 / 召回
provenance = { "ai_generated" : True , "model" : "claude-opus-4-8" , "prompt_id" : "refactor-v3" }
失败模式 :(1)所有 PR 一个 review 强度——高风险审不透、低风险白耗人力。(2)AI 代码与人类代码同等盲信——AI 的错误分布不同(更会「自信地编」),默认信任该更低。(3)无 provenance——某个坏 prompt 量产了 200 个隐患 PR,事后无法定位是哪批、无法批量召回。
进阶资源 · SLSA
Supply-chain provenance / attestation ,
slsa.dev ·
本站 Day 31
Personal AI Safety (blast radius / 可逆性)· Day 34
HITL (审批审计回归)
// 综合实战 · 给一个 legacy repo 装「AI-ready 治理底盘」
一周计划:挑一个你正在维护的混乱大库,按下面顺序装一套不靠堆人力的治理,并验证它真的把 review 从瓶颈里解放出来。
Ratchet :给最痛的一条规则(如 any 数量 / 圈复杂度)建 baseline,CI 只挡新增、变好就锁低点。
Changed-files gate :新规则只对 PR 改动的文件强制,存量豁免冻结。
Fitness function :把一条架构约束(依赖方向 / 禁止 import)写成 CI 测试。
Pre-human gates :测试 + 类型 + secret scan + coverageΔ 全自动,过不了不进人审队列。
Risk routing :写 review_policy()——高风险 full、低风险抽样 + 事后审计,AI 作者默认更严。
Provenance :PR 模板加「AI 生成? / 模型 / prompt_id」字段,建立溯源与召回能力。
复盘 :一周后看 reviewer 的时间花在哪——如果还在读「机器本能查出来」的东西,说明 §3 的门没做够,往前补,别往后加人。
做完这套,你评估任何「AI 写代码」工作流时会本能地先问三件事:机器门挡掉了多少噪音、人审是按风险分级还是一刀切、出事能不能溯源到具体 prompt——而不是纠结「该不该信任 AI 写的代码」这个错问题。正确的问题永远是:信任放在哪一层、由什么来兜。
// ENGLISH GLOSSARY
Strangler Fig 在旧系统边界处长出新结构、逐步替换而非重写的渐进迁移策略。Martin Fowler 命名。
Codemod AST 级的脚本化批量代码变更(jscodeshift / OpenRewrite / ast-grep),替代手工逐文件改。
Ratchet(棘轮) 以当前 violation 数为 baseline,只挡新增、变好即锁定,使欠债单调下降。
Fitness Function 把架构 / 质量约束写成可执行测试,让治理变成 CI 红绿。
Guardrails-as-Code 护栏即代码——规则进 CI 而非 wiki,不依赖人记得。
Pre-human Gate 人审之前的全自动门(测试 / 类型 / 扫描),过不了不进人的队列。
Risk-based Review 按改动风险分级决定人审强度:高风险全审、低风险抽样。
Sampling Review 对低风险变更抽样人审 + 全量事后审计,而非逐个细看。
Provenance / Attestation 记录代码由谁 / 哪个模型 / 哪个 prompt 生成,支持溯源与批量召回。SLSA。
CODEOWNERS 把高风险路径强制绑定指定 owner 的评审机制。
// 深入思考
Ratchet 允许存量违规、只挡新增——技术债会不会永远卡在 baseline 高位还不清? 会,如果只挡不还。Ratchet 的纪律是「不许变糟」,但「变好」需要单独的动力。两个常见做法:(a)把 baseline 下降设成每个 PR 顺手降一点 的软约定(碰到的文件就地修一两个),让债在日常流量里被磨;(b)AI 时代有更强的手——派 agent 批量刷存量(每次一小批、走完整的 §3/§4 门),把「永远没空的重构 sprint」拆成几百个低风险小 PR。Ratchet 保证不退,agent 流量负责前进。两者缺一:只 ratchet 会停在高位,只重构会被新债抵消。
自动门能挡 AI 的低级错,但 AI 最危险的是「看起来完全对的微妙逻辑错」,机器测不出、抽样也漏,怎么办? 这是这套体系的真实边界:自动门 + 抽样只压低可检测错误 和低风险路径 的成本,对「高风险路径上的微妙逻辑错」不能 靠抽样。所以 §4 的风险分级是关键阀门——高风险路径不抽样,全审 ,而且正因为机器把低风险噪音都挡掉了,人才有精力在高风险 PR 上慢下来真读。换句话说,这套不是「让人少审」,是「把省下的注意力重新投到真正需要人脑的地方 」。微妙逻辑错的防线仍是人,只是你确保了人没被琐碎淹没。配合强 property-based / mutation test 能再压一层,但替代不了高风险全审。
抽样评审 = 接受一定比例 AI 代码没人细看就进 main。这和「AI 代码更需要审」不矛盾吗?风险怎么算才不是自欺? 不矛盾,前提是抽样只用在低风险 × 高信任 象限,且有事后审计兜底 。逻辑是:低风险路径(文案 / 测试 / 内部工具)即使错了,blast radius 小、可逆(见 Day 31),用「抽样 + 全量事后审计 + 快速回滚」期望成本低于「逐个全审」的人力成本。会自欺的地方有两个:一是风险分级失真 (把其实高危的路径标成低危)——所以风险表要保守、未分类默认高风险;二是事后审计名存实亡 (抽样了但没人真去 audit)——那就退化成「无人审」。抽样的正当性完全建立在「审计真的在跑、回滚真的够快」之上,否则就是给偷懒找借口。
Provenance 标注「AI 生成」——会不会沦为甩锅工具(出事就怪 AI),而非驱动改进? 取决于你拿它做什么。当「证据」用 = 甩锅(「这是 AI 写的,不怪我」);当「可观测信号 」用 = 改进。后者的具体动作:按 model / prompt_id 聚合缺陷率 ——发现某个 prompt 模板产出的代码缺陷率显著偏高,就去修那个 prompt(回到 Day 34 的审计回归、未来 Day 47 的 prompt-as-code);发现某条高风险路径的 AI 缺陷率高,就把它的信任阈值调严、强制全审。provenance 的价值不在「归咎」,在「让 AI 产出的质量变成可度量、可回归的量 」。出事召回是底线用途,驱动 prompt / 路由改进才是它真正的杠杆。
这套代码治理,和 Day 34 HITL、Day 31 safety 是什么关系?什么时候用哪个? 三者是同一套「信任分层」思想在不同对象上的投影。Day 31 管运行时 的 agent 动作(kill switch / 沙箱 / 不可逆 gate)——防的是 agent 执行 时祸害。Day 34 HITL 是通用的审批编排 (何时问人、怎么异步、怎么接管)——是机制层。本期 是把这套机制具体化到「代码进库」这一个场景 :pre-human gate = confidence routing,风险分级 = impact 轴,provenance = 审计。用哪个看对象:约束 agent 跑动作用 Day 31;搭通用人审工作流用 Day 34;治理「大量 AI 代码涌入一个 legacy 库」这个具体洪流,用本期。它们叠着用,不互斥。
BigCat · Super Individual · Day 33 · 2026-06-15