编码规范文档写了四十页,没人看过第二页。团队通常把它解释成态度问题,但更常见的原因是:它没有任何机制会检查。文档只对愿意遵守的人生效,机制才对所有人生效。可执行化的目标不是把四十页都变成门禁,而是把其中能被判断的那部分,挪到会真正拦住人的位置上。
1. 三类规范与判断方法
判断一条规范能不能自动化,只需要问一个问题:它能不能写成确定性断言——给定一份代码,结果是明确的通过或失败。
| 类别 | 判断方法 | 典型落点 |
|---|---|---|
| 能自动化的 | 能写成确定性断言,不依赖上下文判断 | 格式化器、静态检查 |
| 能半自动化的 | 能检出可疑点,但需要人确认 | 静态检查报警 + 评审确认 |
| 只能靠人的 | 涉及设计判断、取舍、命名意图 | 评审必答问题与决策记录 |
// 能自动化的规范: 写成配置,而不是文档里的句子
style:
formatter: FORMATTER_CONFIG // google-java-format / prettier / black
import_order: enforced
max_file_length: 800
lint:
fail_on: [UNUSED_IMPORT, EMPTY_CATCH, SQL_IN_LOOP]
配置里的规则不需要写成散文,因为它自己会执行;散文里的规则如果没有落点,就只是建议。
三类之间的边界并不稳定:随着检查工具变强,原来"只能靠人"的条目会逐年后移。因此规范的划分不该写死在文档里,而应在每次新增检查能力时重新过一遍——哪些条目现在已经能被断言了。留在"只能靠人"里的条目越多,规范对日常产出的影响就越小。
规范里最不容易被判定的是命名与结构偏好。它们看起来也能写成断言("类名必须是名词"),但断言只能校验形式,校验不了是否准确;这类条目更该出现在评审的必答问题里,而不是门禁里。
2. "必须"与"建议"混写的降级
一份文档里同时写着"必须"和"建议",读者会按最低标准执行。强弱约束混排,等于把强约束降级。
| 措辞 | 机制上读到的含义 | 实际效果 |
|---|---|---|
| 必须 / 禁止 | 有阻断机制吗?没有就是建议 | 与"建议"同级 |
| 建议 / 推荐 | 无阻断,靠自觉 | 会被忽略 |
| 应尽量 | 程度副词,无法判定 | 反复成为争议点 |
| 不允许出现 | 可写成断言 | 能进门禁 |
"不允许出现"这一行是整张表的落点:一条规范若不能改写成这种句式,它就很难进门禁。改写本身也是一次筛选——很多争议条目在尝试改写成断言时会自动消失,因为它们其实只是偏好。
规范条目应当自带理由字段——写不出理由的条目,通常是在规定风格偏好,而不是在防止具体损失:
// 规范条目模板
规则: 禁止在事务方法内发起远程调用
理由: 长事务会持有连接并放大锁等待,历史上造成过连接池耗尽
判据: 静态检查识别注解与调用点(半自动)
例外: 幂等且已加超时的同步查询,需在评审中说明
3. 工具化的四种形态与拦截时机
| 形态 | 拦截时机 | 能自动修 | 代价 |
|---|---|---|---|
| 格式化器 | 保存或提交前 | 能 | 低,几乎没有争议 |
| 静态检查 | 提交前与流水线 | 部分能 | 中,规则越严误报越多 |
| 提交钩子 | 本地提交 | 通常不能 | 低,但可被绕过 |
| 流水线门禁 | 合并前 | 能 | 高,排队与重跑成本 |
"能自动修"与"只能报警"决定了它该放在哪一环:能自动修的放最前面,因为它不打断人;只能报警的放到流水线,因为本地报警会被忽略。
这里有一个具体的差别值得记住:能自动修的规则,冲突根本不该进入讨论——机器改完了,人连看都不用看。只能报警的规则一旦进到本地提交环节,就会变成"提交失败但不知道怎么办",人于是找绕过方式。把报警放流水线,至少还留了一次在评审里解释的机会。
// 提交钩子: 本地第一道,只放能自动修的检查
FORMAT_CMD --check $(CHANGED_FILES) || { echo "格式未通过,先运行 FORMAT_CMD"; exit 1; }
// 流水线门禁: 只放本地做不了的检查(全量扫描、依赖、覆盖率)
gate:
- name: lint-blocking
fail: true
- name: complexity-check
fail: false // 先观察,观察期满再改为 true
- name: full-scan
fail: true
4. 新增规范、存量代码与例外通道
存量代码是绕不过去的:任何一条新规则在生效的第一天,都会大量命中现有代码。上表四个阶段的顺序不能改,因为它的目标只有一个——让规则在没有人对抗的情况下被接受。观察期的存在不是为了收集数据,而是为了让"这条规则会拦下多少东西"变成可讨论的事实,而不是立场。
新增一条规范的正确流程是先观察再阻断:直接上阻断会因为存量代码立刻失败,接着整条门禁被关掉。
| 阶段 | 动作 | 升级条件 |
|---|---|---|
| 观察 | 只报警不阻断,统计命中量 | 命中量收敛到可修复范围 |
| 清理 | 新代码强约束 + 存量豁免 | 豁免条目有到期时间 |
| 阻断 | 门禁改为失败即阻止 | 例外通道已存在 |
| 收敛 | 定期清理过期豁免 | 豁免数走到零附近 |
// 豁免清单: 每条必须有理由与到期日
ALLOW: src/legacy/LEGACY_MODULE // 理由: 计划在退役中;到期: DATE_1
ALLOW: src/old/BIG_FILE // 理由: 拆分排期中;到期: DATE_2
// 到期未处理 → 门禁恢复对它的约束,不允许静默续期
门禁必须留例外通道。没有例外通道的规则会被两种方式绕过:临时关掉整条门禁,或者把违规代码挪到规则覆盖不到的地方。例外要有记录、有期限、有审批人。
例外通道还要能回答一个具体问题:这条豁免到期时,谁会收到通知?如果通知发给豁免申请者,而他早已不在项目里,豁免就会静默过期或静默续期。把到期处理挂到模块 owner 上,和台账里的做法一致——责任跟着代码走。
规范数量与遵守率之间是反向关系:规则越多,人对每条规则的记忆越弱,豁免越多,整体可信度越低。可执行化不是"把四十页都工具化",而是先挑能被判断的那几页,把它们真正做成会拦住人的机制。
5. 排错与边界
5.1 排错
| 现象 | 机制 | 处理 |
|---|---|---|
| 门禁被人为关闭 | 阻断面过大且无绕过路径 | 缩小阻断集,补例外通道 |
| 阻塞率低但违反率不降 | 规则只报警不阻断 | 观察期结束后必须升级为阻断 |
| 格式争议特别多 | 把风格偏好写成了"必须" | 交给格式化器,评审不再讨论该项 |
| 豁免清单不断增长 | 豁免没有到期时间 | 每条豁免加到期日,到期恢复约束 |
| 文档越来越厚但没人读 | 可执行部分与建议混写 | 拆成两份:门禁清单与参考文档 |
可执行化还有一个容易被忽略的副产品:它把"风格问题"从评审里搬走了。评审时间是最稀缺的资源,把它花在缩进、导入顺序这类无争议项上,等于用最贵的人力做最便宜的检查。
排错表里最容易被误读的是"门禁被人为关闭"。关闭通常不是恶意,而是某次发布的阻断面大到无法在窗口内修完,于是有人在流水线配置里把 fail 改成 false,之后再没人改回来。判断标准很简单:只要出现过一次"为了发布而临时关闭",就说明阻断集划得太大,或者例外通道缺失。
5.2 边界
| 场景 | 结论 |
|---|---|
| 团队只有一个人 | 用格式化器与编辑器配置即可,不必建门禁 |
| 代码量极小 | 检查成本高于收益,靠评审更省 |
| 规范主要涉及设计判断 | 工具化收益低,转成评审必答问题 |
| 已有 CI 但无本地检查 | 优先补本地能自动修的检查,缩短反馈时间 |
边界表里"代码量极小"值得补充一句:代码量小时,检查的收益主要来自"防止未来的自己犯错",而不是当前的错误率。如果这份代码一年只改两次,收益确实低于维护成本;但如果是每天都要改的小项目,同一个判断就反过来了。
边界里最容易判断错的是"团队只有一个人"。单人团队其实并不需要门禁,因为它要防的是协作噪声,而单人不存在协作;但它仍然需要格式化器,理由是换编辑器、换机器时的一致性。工具化在这里的收益不在约束别人,而在替自己省记忆。
度量四个数:可执行化比例(规则总数中进了门禁的比例)、阻断命中率、豁免条目数、规范文档篇幅变化。最后一个值得注意——文档变薄通常说明规则真的搬走了。
阻断命中率不是越高越好。持续偏高说明规则与现有写法长期冲突,团队会把它当噪声;持续接近零则要分清是"大家都守规矩"还是"规则其实没生效"——用一次故意构造的违规提交去验证门禁,比看统计更可靠。
// 门禁自检: 用一次人工违规验证它真的会拦
git checkout -b gate-selftest
// 在受检目录放一处已知违规,然后本地跑门禁命令
GATE_CMD $(CHANGED_FILES); echo "exit=$?"
// 期望 exit 非 0;为 0 说明规则未生效或已被静默跳过
// 规范审计(每季度过一遍)
可执行化比例: 进门禁的规则数 / 规则总数
阻断命中率: 被门禁拦下的次数 / 提交次数
豁免条目数: 清单行数,与上一季度对比趋势
文档篇幅: 字数变化,与可执行化比例反向对照
6. 自测
- 判断一条规范能否自动化,只需要问哪一个问题?
- 为什么"必须"与"建议"写在同一份文档里会让强约束降级?
- "能自动修"与"只能报警"分别该放在哪一环,理由是什么?
- 新增一条规范为什么不能直接上阻断?
- 豁免清单为什么必须带到期时间?
- 规范文档篇幅变薄,可能说明了什么?