用途
fail-fast 是一个用于让代码库更确定性的技能。它的目标是找出并移除隐藏的兜底(fallback)行为——那些本应成为显式契约、校验错误、迁移或测试夹具的逻辑。
使用该技能时,应在开始时声明你正在使用 fail-fast 技能。
核心规则
优先采用一条规范路径和一种显式失败模式。
只有当兜底满足以下条件时才允许保留:真实的产品需求、有文档记录的迁移窗口,或有意为之的本地测试夹具。其余一切都应转变为校验错误、必填输入、显式分支、删除的兼容层,或带类型的配置契约。
工作流
- 识别目标仓库及其包管理器。
- 编辑前先运行扫描器(路径相对于该技能目录):
python3 scripts/scan-fallbacks.py /path/to/repo
可用参数:--json(机器可读输出,用于大型审计或后续工具)、--include-tests 和 --include-docs(扫描默认跳过的文件)、--fail-on medium|high(作为 CI 门禁,非零退出)。
python3 scripts/scan-fallbacks.py /path/to/repo --json
当发现涉及环境变量、遗留兼容、宽泛 catch 块或分阶段迁移时,阅读 references/remediation.md。
- 对每个发现进行分类:
- remove:已死的兼容代码、遗留别名、重复的选项名、没有活跃调用者的兜底分支。
- require:缺失的配置、缺失的依赖、无效的用户输入,或应属于环境 schema 的环境变量。
- validate:必须接受未知数据但应清晰拒绝无效状态的边界输入。
- keep:有文档记录的产品行为、真实的外部 API 兼容,或带有负责人和移除日期的临时迁移。
- 窄范围编辑。移除兜底状态,并更新调用点/测试以使用规范路径。
- 添加或更新测试,证明当依赖、输入、配置或 schema 声明的环境变量缺失时,代码会确定性地失败。
- 再次运行扫描器,以及仓库相关的测试、类型检查、lint 或构建。
- 显式报告剩余的兜底。不要让它们不可见。
环境变量
环境变量是配置契约,不是建议字段。
在具有已验证环境 schema 的项目中,必填值应在应用代码使用它们之前通过校验失败。从项目的环境模块导入带类型的值。如果 schema 中缺少某个必填键,应在那里声明并校验,而不是添加应用层兜底。
使用项目现有的环境校验和构建检查。查看其脚本和校验器文档以获取实际命令;不要仅为遵循此技能而安装新包。
仅在以下情况接受直接读取 process.env:环境 schema 模块、生成的环境接线、诸如 NODE_ENV 和 CI 之类的窄系统键,或明确记录的迁移逃生舱。
移除如下模式:
const apiKey = process.env.OPENAI_API_KEY || "dev-key";
const databaseUrl = process.env.DATABASE_URL ?? "postgres://localhost";
替换为带类型的环境访问:
const apiKey = env.OPENAI_API_KEY;
当不存在环境 schema 时,使用项目现有约定在配置边界显式校验必填值。
错误处理
fail fast 不意味着模糊地崩溃。它意味着在边界处以有用的、带类型的或带上下文的错误失败。
优先使用:
- 对外部输入进行 schema 校验,
- 对不可能的内部状态显式抛出错误,
- 窄 catch 块,带上下文重新抛出,
- 对必填配置和服务的启动预检。
避免:
- 空 catch 块,
- 返回虚假成功的 catch 块,
- 静默降级必需功能的可选依赖导入,
- 让测试通过但生产行为已损坏的兜底数据。
兼容性
向后兼容必须是有意为之的。
区分永久支持的契约与临时迁移。保留公共 API、协议或受支持产品行为所需的兼容;它不需要移除日期或逐个命名的消费者。临时兼容应有负责人、迁移窗口和两条路径的检查。缺少文档是调查并记录契约的理由,而不是删除安全的证明。只有在确认没有受支持的调用者或契约仍需要它们之后,才移除别名、遗留分支和适配器。
当兼容必须保留时,让它可见:
// TODO(compat): remove after API clients stop sending `workspaceId` on 2026-07-01.
让标记以 TODO/FIXME/HACK 开头,以便扫描器的 todo-compat 规则能发现它——单独的 // Compatibility: 注释有意不被标记(关键词规则会跳过注释行)。
扫描器
scripts/scan-fallbacks.py 对常见兜底气味执行确定性源码扫描:
- 环境变量默认值(
os.getenv/os.environ.get/process.env.X ||)和直接环境读取, ||/??兜底值,- legacy、deprecated、backwards-compatible 和 migration 关键词,
- 空 catch 或吞异常 catch 块(JS/TS)以及吞异常 except 块(Python),
- 可选依赖兜底,
- 别名和双配置键。
检测注意事项:legacy/兼容关键词规则只在代码行触发,不在注释行触发,因此单独的 // Compatibility: ... 标记有意不被标记。临时兼容注释只有在以 TODO/FIXME/HACK/XXX 开头时才会被呈现(todo-compat 规则)——参见兼容性部分的标记格式。关键词规则还会跳过声明式清单(package.json、tsconfig.json),以避免匹配依赖名。--fail-on 只接受 medium 或 high(没有低严重度规则)——之前接受的 --fail-on low 现在会以 argparse 错误退出,因此请更新任何传入它的脚本。
扫描器不能替代判断。把它当作需要检查的位置索引,然后让代码更简单、更确定性。
完成检查
在结束前,验证:
- 每个发现都被分类(remove / require / validate / keep),而不仅仅是列出。
- 被移除的兜底已更新调用点和测试,证明当依赖、输入、配置或环境变量缺失时会确定性失败。
- 保留的兜底有明确理由(产品需求、外部契约,或带负责人和日期的迁移)。
- 编辑后重新运行了扫描器以及仓库相关的测试/类型检查/lint/构建。
- 任何剩余的兜底都被显式报告,而不是不可见。