B{"出现走捷径的冲动"} B --> C["生成自然语言借口"] C --> D["Anti-Rationalization匹配并反驳"] B --> E["行为已经越线"] E --> F["Red Flags识别违规模式"] D --> G["回到必要步骤"] F --> G G --> H{"证据门SPEC / 测试 / 构建 / 健康检查"} H -->|通过| I["允许完成或部署"] H -->|失败| J["修正后重新验证"] J --> G注意最后一层是“证据门”。自然语言可以劝模型别跳步,但真正决定能不能继续的,应该是文件、测试结果、构建退出码和线上健康检查,而不是一句“我已经确认”。## 六个最常见的借口,逐个拆下面六句在 AI 编码对话里很常见。它们不一定逐字出现在某一个 SKILL.md 中,但都对应仓库里反复出现的真实模式。| 典型借口 | 被藏起来的问题 | 强制反驳 ||—|—|—|| “这个改动太小,跳过测试没关系” | 用 diff 大小替代风险判断 | 判断行为是否变化;变化了就要有证据 || “用户已经说清楚了,不用写 SPEC” | 把输入清楚等同于完成标准清楚 | 至少写目标、非目标和验收条件 || “我先快速实现,之后再重构” | 把没有期限的以后包装成计划 | GREEN 后立即在测试保护下 REFACTOR || “这次不一样,特殊情况” | 只声明例外,不证明例外 | 点名适用的豁免条款并给出证据 || “之前的代码就是这么写的” | 把历史事实当成正确性证明 | 先确认约束,再决定沿用还是修正 || “现在没时间,等上线后再补” | 把质量成本转移到生产环境 | 缩范围、延发布,不能借走验证时间 |### 1. “这个改动太小,跳过测试没关系”改动大小不是风险单位,行为变化和失败后果才是。一行 shell 命令可以清空目录,一字符 frontmatter 错误可以让文章不生成。test-driven-development 也没有要求所有文件都写单元测试:纯文档、没有行为影响的静态内容可以不走 TDD。关键是先证明它真的属于豁免,而不是看到“一行”就自动放行。正确动作可以很轻。改了 shell 脚本,先跑 bash -n deploy.sh;改了 Hexo 内容,至少跑一次完整生成;改了逻辑,先写能失败的测试。小改动对应小验证,不对应零验证。### 2. “用户已经说清楚了,不用写 SPEC”“做什么”清楚,不代表“做到什么程度算完成”也清楚。用户说“部署博客”,仍然没有回答:从哪个分支部署、构建失败能否覆盖线上目录、是否必须备份、上线后检查哪个 URL。SPEC 不等于十页 PRD。对一个明确的小任务,下面三行已经够用:反合理化机制反对的是“没有可验收定义就开工”,不是强迫所有改动写长文档。### 3. “我先快速实现,之后再重构”这句话最会伪装,因为 TDD 本来就有 REFACTOR。区别在于,TDD 的顺序是 RED → GREEN → REFACTOR,重构紧跟在绿灯之后,而且每一步都有测试保护。“之后再重构”通常没有时间点、触发条件和负责人,翻译一下就是“先把复杂度留在主干”。如果现在确实只需要最小实现,那就把“最小”写进任务边界,并在同一个任务里完成必要清理。超出本次范围的重构,要变成有验收条件的独立任务,而不是聊天里的口头欠条。### 4. “这次不一样,特殊情况”真实工程当然有例外。线上故障止血、纯文案修正、无法稳定复现的第三方问题,都可能需要不同流程。问题不在例外,而在“特殊”两个字经常替代了论证。遇到这句话,强制追问三件事:哪条条件不同、证据在哪里、因此可以跳过哪一步。比如“这是静态文案,不改变任何运行行为,所以不写单元测试;但仍运行 Hexo 构建确认 Markdown 可渲染”。能这样说清楚,才叫有边界的例外。### 5. “之前的代码就是这么写的”现有代码能证明“这个模式存在”,不能证明“这个模式正确”。它可能是兼容约束,也可能只是多年前没人清理的债。照抄错误模式,会让局部一致性压过系统正确性。先查相邻代码、项目约定和测试,再做选择。如果旧写法受兼容性约束,就记录约束并沿用;如果只是历史遗留,就不要把它扩散到新代码。无论哪种选择,都要用当前需求的测试来证明,而不是用 git blame 当验收报告。### 6. “现在没时间,等上线后再补”上线后通常不会突然多出时间,只会多出告警、用户反馈和下一项需求。shipping-and-launch 对“监控以后再加”的反驳很直接:看不见的系统无法调试;等用户投诉才知道出事,已经太晚。时间不足时,正确的变量是范围和发布日期。可以少发一个功能,可以先灰度,可以延后发布;不能把测试、回滚和健康检查一起删掉。赶时间更需要小批次和可逆发布,因为此时人的注意力最差。## Red Flags:不听解释,只看行为反合理化表处理“模型说了什么”,Red Flags 处理“模型做了什么”。有些违规不会先说出口:模型可能直接开始改代码,最后才用一句“需求很明确”补解释。所以红色信号必须写成可观察行为。| Red Flag | 它说明什么 | 立即动作 ||—|—|—|| 没有任何书面需求就开始写代码 | SPEC 门被绕过 | 停止实现,补目标与验收条件 || 测试第一次运行就通过 | RED 阶段可能无效 | 检查测试是否真能捕获目标行为 || 声称“All tests pass”却没有命令输出 | 把判断冒充证据 | 运行仓库实际测试命令并保留结果 || Bug 修复没有复现测试 | 修复无法防止回归 | 先写能展示旧 Bug 的失败测试 || 为了绿灯跳过、删除或放宽测试 | 门控被反向修改 | 恢复测试,修代码或解释需求变更 || 未看项目配置就直接跑 npm test | 用默认习惯替代仓库事实 | 先读 package.json、CI 和邻近测试 || 代码未变化却连续重复同一测试 | 在表演谨慎,不是在增加证据 | 停止重复;只在状态变化后重跑 || 部署后没有健康检查和回滚路径 | “复制完成”被误当成上线成功 | 检查线上行为,准备恢复旧版本 |Red Flag 一旦出现,不该只打印一句 warning 然后继续。它的语义应该是:当前阶段无效,退回上一个门控。测试首次就通过,要回到 RED 检查测试;上线无健康检查,要回到发布验证,而不是在总结里轻描淡写地说“建议后续补充”。## 为什么自然语言理由能补上 lint 的盲区lint、类型检查和规则引擎很重要,但它们检查的是已经存在的工件。ESLint 能发现未使用变量,ShellCheck 能发现危险引用,CI 能根据退出码阻止合并;它们很难判断“模型为什么没写测试”,更看不到一句“这个太简单了”。| 维度 | lint / 规则引擎 | Anti-Rationalization / Red Flags ||—|—|—|| 检查对象 | 源码、配置、AST、命令结果 | 对话、计划、工具调用和行为顺序 || 擅长问题 | 可枚举、可机器判定的违规 | 语义相同但说法多变的规避理由 || 生效时机 | 工件产生后 | 决策形成时和步骤越线时 || 判断方式 | 确定规则、正则、类型系统 | 上下文中的自然语言推理 || 弱点 | 看不到意图和缺失的步骤 | 可能被模型再次合理化,不能单独当硬门 |为什么这里自然语言比正则更有效?因为“改动很小”“只是一个小修”“没必要为两行代码跑套件”在字面上不同,语义上却是同一个借口。LLM 能在它正在使用的语义空间里匹配这些变体,再读到对应反驳。正则若想覆盖所有说法,很快就会变成漏网清单。但不要把结论说反了:自然语言不是 lint 的替代品。最稳的组合是“自然语言阻止错误决策,自动化门控阻止错误结果”。能写成确定规则的,就交给 CI;只有上下文才能判断的,才交给技能。## 实战:把它放进这个博客的 deploy.sh本博客的 deploy.sh 已经有不少好基础:set -euo pipefail 让未处理错误立刻终止;git pull --ff-only 避免部署机生成意外合并;构建前执行 hexo clean 和 hexo generate;删除目标目录时用 ${DEPLOY_DIR:?} 防止空变量误删;还提供了 --backup。但它也暴露了几处很适合反合理化机制的缝:public/ 存在不等于构建内容完整;文件复制成功不等于 Nginx 已能正常提供页面;备份是可选项,而部署步骤会先清空 /var/www/blog;日志里的文件数是结果信息,不是线上健康检查。先给这条部署链写一份最小门控,不需要改成庞大的发布平台:部署前的证据可以直接落成命令:上线时再补两道门:现在把典型借口放进来,就能看到机制怎么工作:| 部署时刻 | 借口 | Reality | 必须执行的门 ||—|—|—|—|| 改了一篇 Markdown | “只是文字,不用验证” | frontmatter、主题过滤器和 Mermaid 都可能让生成失败 | 完整运行 hexo generate || 脚本用了 set -e | “失败会自动停,够安全了” | 它只知道命令退出码,不知道页面是否正确 | 检查产物并请求线上 URL || public/ 目录存在 | “构建结果肯定在” | 空目录、缺首页、部分生成都可能存在 | 检查 index.html 和 HTML 数量 || 内容改动很小 | “这次不用备份” | 清空目标目录的破坏性与改动大小无关 | 生产执行 --backup || cp 返回 0 | “部署完成” | 权限、Nginx 配置和缓存仍可能出问题 | curl --fail,失败就回滚 |这里还有一个值得标成 Red Flag 的动作:脚本先 rm -rf 再 cp,中间存在短暂空目录窗口。对个人博客可能能接受,但不能用“流量小”把它说成不存在。更稳的下一步是构建到版本目录,再用原子切换发布;是否要做,可以由可用性目标决定。## 最后:把“完成”从一句话改成证据Anti-Rationalization Table 的价值,不是让 AI 更啰嗦,也不是把每个任务都套上最重流程。它做的是提前识别那些听起来很专业、实际在绕门的句子。Red Flags 再从行为侧兜底,防止模型不解释就直接越线。真正有效的顺序是:借口出现 → 强制反驳 → 回到步骤 → 产出证据 → 自动门控放行。只写“不要偷懒”没有用,只上 lint 也不够。把自然语言约束和可执行证据接起来,AI 才不是“答应遵守流程”,而是真的没有借口绕过去。">昨天讲 DEFINE 三件套时,我们碰到了一张很特别的表:左边写 AI 可能给出的借口,右边提前写好反驳。它在 agent-skills 里叫 Common Rationalizations,也就是本文说的 Anti-Rationalization Table。
这张表不是项目 FAQ,更不是写给用户看的鸡汤。它是塞进技能上下文、专门给 AI 看的“强制反驳清单”。今天我们把它和 Red Flags 一起拆开,再放进这个博客的 Hexo 部署流程里走一遍。

## Anti-Rationalization 不是提醒,而是预先反驳
AI 并不会真的坐在那里盘算“怎么偷懒”。更准确地说,它会偏向一条看起来最顺、反馈最快、工具调用最少的路径。写代码能立刻产生可见进展,补 SPEC、先写失败测试、跑完整构建则像是在“拖慢任务”。于是,只要约束不够明确,模型就很容易为捷径生成一个听起来合理的解释。
spec-driven-development 里的写法很直接:
| Rationalization | Reality |
|—|—|
| “这件事很简单,不需要 SPEC” | 简单任务不需要长 SPEC,但仍需要验收标准;两行也可以 |
| “我写完代码再补 SPEC” | 那叫文档,不叫规约;规约的价值正是在编码前迫使需求变清楚 |
| “用户知道自己想要什么” | 再清楚的请求也有隐含假设,SPEC 要把假设翻出来 |
关键不在于表格形式,而在于它做了三件事:复述借口、指出现实、把下一步重新钉回流程。普通规则只说“必须写 SPEC”,反合理化表则继续回答“为什么你刚想到的例外不成立”。它把模型下一步可能生成的自我辩护也纳入了提示词。
一张好用的反合理化表通常有三个设计要求:
1. 借口要像真实对话。 写“这个改动只有一行”比写“不得规避流程”更容易命中模型正在形成的理由。
2. 反驳要说明因果。 不是“因为规定如此”,而是“小改动同样会改变行为,测试成本低于回归成本”。
3. 反驳后要有动作。 回到失败测试、补两行验收标准、运行仓库自己的构建命令,而不是停在价值判断上。
它和 Red Flags 组成了两层防线:前者拦“理由”,后者抓“行为”。
flowchart LR
A["收到任务"] --> B{"出现走捷径的冲动"}
B --> C["生成自然语言借口"]
C --> D["Anti-Rationalization
匹配并反驳"]
B --> E["行为已经越线"]
E --> F["Red Flags
识别违规模式"]
D --> G["回到必要步骤"]
F --> G
G --> H{"证据门
SPEC / 测试 / 构建 / 健康检查"}
H -->|通过| I["允许完成或部署"]
H -->|失败| J["修正后重新验证"]
J --> G

注意最后一层是“证据门”。自然语言可以劝模型别跳步,但真正决定能不能继续的,应该是文件、测试结果、构建退出码和线上健康检查,而不是一句“我已经确认”。
## 六个最常见的借口,逐个拆
下面六句在 AI 编码对话里很常见。它们不一定逐字出现在某一个 SKILL.md 中,但都对应仓库里反复出现的真实模式。
| 典型借口 | 被藏起来的问题 | 强制反驳 |
|—|—|—|
| “这个改动太小,跳过测试没关系” | 用 diff 大小替代风险判断 | 判断行为是否变化;变化了就要有证据 |
| “用户已经说清楚了,不用写 SPEC” | 把输入清楚等同于完成标准清楚 | 至少写目标、非目标和验收条件 |
| “我先快速实现,之后再重构” | 把没有期限的以后包装成计划 | GREEN 后立即在测试保护下 REFACTOR |
| “这次不一样,特殊情况” | 只声明例外,不证明例外 | 点名适用的豁免条款并给出证据 |
| “之前的代码就是这么写的” | 把历史事实当成正确性证明 | 先确认约束,再决定沿用还是修正 |
| “现在没时间,等上线后再补” | 把质量成本转移到生产环境 | 缩范围、延发布,不能借走验证时间 |
### 1. “这个改动太小,跳过测试没关系”
改动大小不是风险单位,行为变化和失败后果才是。一行 shell 命令可以清空目录,一字符 frontmatter 错误可以让文章不生成。test-driven-development 也没有要求所有文件都写单元测试:纯文档、没有行为影响的静态内容可以不走 TDD。关键是先证明它真的属于豁免,而不是看到“一行”就自动放行。
正确动作可以很轻。改了 shell 脚本,先跑 bash -n deploy.sh;改了 Hexo 内容,至少跑一次完整生成;改了逻辑,先写能失败的测试。小改动对应小验证,不对应零验证。
### 2. “用户已经说清楚了,不用写 SPEC”
“做什么”清楚,不代表“做到什么程度算完成”也清楚。用户说“部署博客”,仍然没有回答:从哪个分支部署、构建失败能否覆盖线上目录、是否必须备份、上线后检查哪个 URL。
SPEC 不等于十页 PRD。对一个明确的小任务,下面三行已经够用:
1
2
3
目标:发布 main 分支当前版本,首页可访问。
非目标:本次不调整 Nginx 配置和主题。
验收:Hexo 构建成功;目标目录非空;线上首页返回 2xx。

反合理化机制反对的是“没有可验收定义就开工”,不是强迫所有改动写长文档。
### 3. “我先快速实现,之后再重构”
这句话最会伪装,因为 TDD 本来就有 REFACTOR。区别在于,TDD 的顺序是 RED → GREEN → REFACTOR,重构紧跟在绿灯之后,而且每一步都有测试保护。“之后再重构”通常没有时间点、触发条件和负责人,翻译一下就是“先把复杂度留在主干”。
如果现在确实只需要最小实现,那就把“最小”写进任务边界,并在同一个任务里完成必要清理。超出本次范围的重构,要变成有验收条件的独立任务,而不是聊天里的口头欠条。
### 4. “这次不一样,特殊情况”
真实工程当然有例外。线上故障止血、纯文案修正、无法稳定复现的第三方问题,都可能需要不同流程。问题不在例外,而在“特殊”两个字经常替代了论证。
遇到这句话,强制追问三件事:哪条条件不同、证据在哪里、因此可以跳过哪一步。比如“这是静态文案,不改变任何运行行为,所以不写单元测试;但仍运行 Hexo 构建确认 Markdown 可渲染”。能这样说清楚,才叫有边界的例外。
### 5. “之前的代码就是这么写的”
现有代码能证明“这个模式存在”,不能证明“这个模式正确”。它可能是兼容约束,也可能只是多年前没人清理的债。照抄错误模式,会让局部一致性压过系统正确性。
先查相邻代码、项目约定和测试,再做选择。如果旧写法受兼容性约束,就记录约束并沿用;如果只是历史遗留,就不要把它扩散到新代码。无论哪种选择,都要用当前需求的测试来证明,而不是用 git blame 当验收报告。
### 6. “现在没时间,等上线后再补”
上线后通常不会突然多出时间,只会多出告警、用户反馈和下一项需求。shipping-and-launch 对“监控以后再加”的反驳很直接:看不见的系统无法调试;等用户投诉才知道出事,已经太晚。
时间不足时,正确的变量是范围和发布日期。可以少发一个功能,可以先灰度,可以延后发布;不能把测试、回滚和健康检查一起删掉。赶时间更需要小批次和可逆发布,因为此时人的注意力最差。
## Red Flags:不听解释,只看行为
反合理化表处理“模型说了什么”,Red Flags 处理“模型做了什么”。有些违规不会先说出口:模型可能直接开始改代码,最后才用一句“需求很明确”补解释。所以红色信号必须写成可观察行为。
| Red Flag | 它说明什么 | 立即动作 |
|—|—|—|
| 没有任何书面需求就开始写代码 | SPEC 门被绕过 | 停止实现,补目标与验收条件 |
| 测试第一次运行就通过 | RED 阶段可能无效 | 检查测试是否真能捕获目标行为 |
| 声称“All tests pass”却没有命令输出 | 把判断冒充证据 | 运行仓库实际测试命令并保留结果 |
| Bug 修复没有复现测试 | 修复无法防止回归 | 先写能展示旧 Bug 的失败测试 |
| 为了绿灯跳过、删除或放宽测试 | 门控被反向修改 | 恢复测试,修代码或解释需求变更 |
| 未看项目配置就直接跑 npm test | 用默认习惯替代仓库事实 | 先读 package.json、CI 和邻近测试 |
| 代码未变化却连续重复同一测试 | 在表演谨慎,不是在增加证据 | 停止重复;只在状态变化后重跑 |
| 部署后没有健康检查和回滚路径 | “复制完成”被误当成上线成功 | 检查线上行为,准备恢复旧版本 |
Red Flag 一旦出现,不该只打印一句 warning 然后继续。它的语义应该是:当前阶段无效,退回上一个门控。测试首次就通过,要回到 RED 检查测试;上线无健康检查,要回到发布验证,而不是在总结里轻描淡写地说“建议后续补充”。
## 为什么自然语言理由能补上 lint 的盲区
lint、类型检查和规则引擎很重要,但它们检查的是已经存在的工件。ESLint 能发现未使用变量,ShellCheck 能发现危险引用,CI 能根据退出码阻止合并;它们很难判断“模型为什么没写测试”,更看不到一句“这个太简单了”。
| 维度 | lint / 规则引擎 | Anti-Rationalization / Red Flags |
|—|—|—|
| 检查对象 | 源码、配置、AST、命令结果 | 对话、计划、工具调用和行为顺序 |
| 擅长问题 | 可枚举、可机器判定的违规 | 语义相同但说法多变的规避理由 |
| 生效时机 | 工件产生后 | 决策形成时和步骤越线时 |
| 判断方式 | 确定规则、正则、类型系统 | 上下文中的自然语言推理 |
| 弱点 | 看不到意图和缺失的步骤 | 可能被模型再次合理化,不能单独当硬门 |
为什么这里自然语言比正则更有效?因为“改动很小”“只是一个小修”“没必要为两行代码跑套件”在字面上不同,语义上却是同一个借口。LLM 能在它正在使用的语义空间里匹配这些变体,再读到对应反驳。正则若想覆盖所有说法,很快就会变成漏网清单。
但不要把结论说反了:自然语言不是 lint 的替代品。最稳的组合是“自然语言阻止错误决策,自动化门控阻止错误结果”。能写成确定规则的,就交给 CI;只有上下文才能判断的,才交给技能。
## 实战:把它放进这个博客的 deploy.sh
本博客的 deploy.sh 已经有不少好基础:set -euo pipefail 让未处理错误立刻终止;git pull --ff-only 避免部署机生成意外合并;构建前执行 hexo cleanhexo generate;删除目标目录时用 ${DEPLOY_DIR:?} 防止空变量误删;还提供了 --backup
但它也暴露了几处很适合反合理化机制的缝:public/ 存在不等于构建内容完整;文件复制成功不等于 Nginx 已能正常提供页面;备份是可选项,而部署步骤会先清空 /var/www/blog;日志里的文件数是结果信息,不是线上健康检查。
先给这条部署链写一份最小门控,不需要改成庞大的发布平台:
1
2
3
4
5
PREPARE:脚本语法正确,依赖可重复安装。
BUILD:Hexo clean + generate 成功。
VERIFY:public/index.html 存在且非空,至少生成一个 HTML 文件。
DEPLOY:生产部署必须带备份,复制失败立即停止。
OBSERVE:首页返回 2xx;失败时恢复备份并再次检查。

部署前的证据可以直接落成命令:
1
2
3
4
5
6
bash -n deploy.sh
npm ci --no-audit --no-fund
npx hexo clean
npx hexo generate
test -s public/index.html
test -n "$(find public -type f -name '*.html' -print -quit)"

上线时再补两道门:
1
2
./deploy.sh --backup
curl --fail --silent --show-error http://blog.lidongshan.com/ >/dev/null

现在把典型借口放进来,就能看到机制怎么工作:
| 部署时刻 | 借口 | Reality | 必须执行的门 |
|—|—|—|—|
| 改了一篇 Markdown | “只是文字,不用验证” | frontmatter、主题过滤器和 Mermaid 都可能让生成失败 | 完整运行 hexo generate |
| 脚本用了 set -e | “失败会自动停,够安全了” | 它只知道命令退出码,不知道页面是否正确 | 检查产物并请求线上 URL |
| public/ 目录存在 | “构建结果肯定在” | 空目录、缺首页、部分生成都可能存在 | 检查 index.html 和 HTML 数量 |
| 内容改动很小 | “这次不用备份” | 清空目标目录的破坏性与改动大小无关 | 生产执行 --backup |
| cp 返回 0 | “部署完成” | 权限、Nginx 配置和缓存仍可能出问题 | curl --fail,失败就回滚 |
这里还有一个值得标成 Red Flag 的动作:脚本先 rm -rfcp,中间存在短暂空目录窗口。对个人博客可能能接受,但不能用“流量小”把它说成不存在。更稳的下一步是构建到版本目录,再用原子切换发布;是否要做,可以由可用性目标决定。
## 最后:把“完成”从一句话改成证据
Anti-Rationalization Table 的价值,不是让 AI 更啰嗦,也不是把每个任务都套上最重流程。它做的是提前识别那些听起来很专业、实际在绕门的句子。Red Flags 再从行为侧兜底,防止模型不解释就直接越线。
真正有效的顺序是:借口出现 → 强制反驳 → 回到步骤 → 产出证据 → 自动门控放行。只写“不要偷懒”没有用,只上 lint 也不够。把自然语言约束和可执行证据接起来,AI 才不是“答应遵守流程”,而是真的没有借口绕过去。

参考资料