基于Andrej Karpathy对LLM编码陷阱的观察,提供5条核心编码原则。
关键算法先写不变量,再写码。正确性先于简洁。
编写递归、图遍历、状态机、排序等正确性敏感算法前必须:
反模式:
正确做法:
环检测不变量:
- 前置:图节点集合 G,遍历方向 dir ∈ {down, up, both}
- 不变量:递归栈 stack 中任意节点 v,其下游环已被正确计数且 v 不重复计入
- 后置:返回环数与环成员,遍历方向与边有向性解耦
→ 先有这张表,再写 DFS
符号/边抽取不变量(静态分析类):
- 前置:① 源码在抽取前已剥离注释/死代码,或抽取层显式排除注释/字符串字面量;② 抽取层按语言隔离——某语言的专属语法正则(如 PHP 的 namespace/use/class/function/new/::)只对该语言文件运行,非目标语言文件(.py/.js/.ts/.java 等)不得套用,否则 .py 的 `class X:`、.js 的 ES6 `class X {}` 会被误抽成目标语言符号污染符号表
- 不变量:抽取出的每个符号 s ∈ 真实语句 token 集;∀ 边 e=(a,b),a,b 均在真实代码中定义或被引用;且 s 所属语法恰好属于当前文件的目标语言
- 后置:symbols/edges 中无来自注释/死代码/字符串字面量的伪符号或假边,也无跨语言误抽(非目标语言文件不含目标语言专属符号)
→ 先有这张表,再写抽取正则(漏剥离任一层、或漏语言隔离,断言都会立刻暴露 phantom/跨语言误抽,而非上线后静默成假边)
原则0 优先于其余四条:简洁、精准、目标驱动都不能以牺牲正确性为代价。
原则0 要求"先写不变量再写码",但没讲不变量怎么来。任何图遍历 / 递归 / 状态机 / 排序 / 依赖解析算法,先按下面三步把不变量显式推出来,再动码——边界用例表(见 test-generation.md)是"授人以鱼",这三步是"授人以渔",P8 面对任意新算法能自己推出不变量,靠的是这个。
第一步:找循环保持量(Loop Invariant)
定义:每次循环 / 递归迭代前后都恒真的命题。
连问三句:
第二步:找终止度量(Termination Variant)
定义:每次迭代严格递减、且有下界的量。
问一句:"每次递归,什么量在严格变小?它的下界是什么?"
第三步:找状态守恒(State Conservation)
定义:算法维护的多个状态量之间必须满足的守恒关系。
问一句:"哪些量必须守恒?违反守恒就等于出 bug?"
推导自检清单(交出草稿不变量后,先自证再写码):
落地硬规则:遇到关键算法,先交出「不变量命题 + 终止度量 + 守恒关系」三件套;交不出、或过不了自检清单,禁止开始写码,也禁止声称"修复完成"。
不要假设。不要隐藏困惑。暴露权衡。
编码前必须:
反模式:
正确做法:
导出用户数据前,我需要澄清:
1. 范围:导出全部用户还是筛选后的子集?
2. 格式:JSON/CSV/直接API返回?
3. 字段:哪些字段?(有些可能涉及隐私)
最少代码解决问题。不要 speculative。
自检问题:"资深工程师会说这过度复杂吗?"如果是,简化。
反模式:
正确做法:
# 简单直接
def calculate_discount(amount: float, percent: float) -> float:
return amount * (percent / 100)
只碰必须碰的。只清理自己制造的。
编辑现有代码时:
你的修改制造孤儿时:
测试标准:每一行修改都能追溯到用户的请求。
反模式:
正确做法:
- if not user_data.get('email'):
+ email = user_data.get('email', '')
+ if not email or not email.strip():
raise ValueError("Email required")
定义成功标准。循环直到验证。
把指令式任务转化为可验证目标:
多步骤任务时,陈述简要计划:
1. [步骤] → 验证:[检查]
2. [步骤] → 验证:[检查]
3. [步骤] → 验证:[检查]
强成功标准让你独立循环。弱标准("让它工作")需要持续澄清。
| 失败场景 | 回退目标 | 最大重试 | | - | - | - | | 目标模糊("让它工作"类) | 拆分为可验证子项,附验证方法 | 2 | | 歧义无法澄清 | 列2-3种最可能理解,标注"假设" | 2 | | 需求边界模糊 | 保守保留核心功能,标注"待确认" | 1 | | 测试环境/依赖不可用 | 最小化方式验证核心逻辑(直接调用+断言) | 2 | | 验证失败(外部依赖问题) | 标注"外部依赖异常",记录复现条件 | 1 | | 连续3轮未通过验证 | 暂停循环,产出当前最佳版本+差异报告 | 0 |
不可恢复时输出阻塞清单和升级路径,由用户决策。
## Karpathy编码规范评估
**评估代码**:[文件名/片段]
### 原则0:先证后写
- [ ] 是否涉及正确性敏感算法(递归/图遍历/状态机/排序)?
- [ ] 关键算法是否先写不变量、前置/后置/终止条件?
- [ ] 是否用结构化断言验证不变量,而非"流程跑通"?
**评估**:[通过/需改进/不适用]
**问题**:[具体问题]
**建议**:[改进建议]
### 原则1:编码前思考
- [ ] 假设是否明确陈述?
- [ ] 是否存在未澄清的歧义?
- [ ] 是否遗漏了更简单的方案?
**评估**:[通过/需改进]
**问题**:[具体问题]
**建议**:[改进建议]
### 原则2:简洁优先
- [ ] 是否包含超出需求的功能?
- [ ] 是否存在单次使用的抽象?
- [ ] 是否可以更短更简单?
**评估**:[通过/需改进]
**问题**:[具体问题]
**建议**:[简化方案]
### 原则3:精准修改
- [ ] 是否只修改了必要的代码?
- [ ] 是否保持了原有风格?
- [ ] 是否清理了自己的孤儿代码?
**评估**:[通过/需改进]
**问题**:[具体问题]
**建议**:[改进建议]
### 原则4:目标驱动执行
- [ ] 是否有明确的验证标准?
- [ ] 是否可以独立验证每一步?
**评估**:[通过/需改进]
**问题**:[具体问题]
**建议**:[改进建议]
## 总体评估
**符合度**:[X]/100
**主要问题**:[Top 1-2问题]
**优先修复**:[具体建议]
生成或审查代码时,以下模式必须作为质量门禁逐项排查:
| 门禁项 | 典型表现 | 处理规则 |
| - | - | - |
| 过度抽象 | 为一次性逻辑创建策略模式、工厂、管理器、配置层 | 删除抽象,回到直接、可读、可测的实现 |
| 废注释 | 注释重复代码含义、为显而易见的赋值写解释 | 删除废注释,仅保留解释业务约束、边界条件、危险操作的注释 |
| 模板化错误处理 | 每个函数机械包 try/catch,catch 内吞异常或只返回空值 | 让异常在合适层级处理;catch 必须记录、转换或给用户可见反馈 |
| 防御式臆想 | 为未出现的输入、未要求的插件点、未知未来需求堆分支 | 删除未被需求或测试证明的分支 |
| 全文件重写 | 为小修复重排格式、重命名无关变量、搬动无关函数 | 回退到局部修改,只触碰完成目标所需代码 |
| 不可验证产物 | 只给代码,不给 lint/test/build/curl/手动验收路径 | 补齐最小验证命令或说明无法验证的具体阻塞 |
交付前必须回答:
交付关键算法或修复前,以下静态检查作为硬门禁,必须通过或显式说明豁免理由:
| 门禁项 | 工具/动作 | 处理规则 |
| - | - | - |
| Lint | 语言对应 linter(PHP php -l、ESLint、ruff、golangci-lint 等) | 语法/静态错误 0 容忍,必须本地跑通后再交付 |
| 类型检查 | mypy / tsc --noEmit / phpstan 等 | 类型错误必须修复或标注 # type: ignore 并说明原因 |
| 死代码检测 | vulture / pyflakes / IDE 未使用检测 | 删除未使用 import/变量/函数,或说明保留理由 |
| 测试门禁 | 关键算法必须跑通算法边界用例(见 test-generation.md) | 结构边界用例不过,不得声称"修复完成" |
未跑静态分析门禁即交付,视为未验证产物,按「反 AI 糟粕代码门禁」的"不可验证产物"处置。
新增、升级或锁定依赖时,必须遵守以下规则,避免构建漂移和环境差异。
| 规则 | 说明 | 反例 |
| - | - | - |
| 版本锁定 | 所有依赖必须使用锁文件精确锁定版本 | composer.json 写 "laravel/framework": "^10.0" 但未提交 composer.lock |
| 禁止浮动版本 | 业务代码禁止使用 latest / * / dev-main / @stable 等浮动约束 | "vue": "latest" |
| 锁文件入库 | composer.lock / package-lock.json / pnpm-lock.yaml / yarn.lock 必须提交到代码库 | .gitignore 排除锁文件 |
| 子依赖锁定 | 不依赖平台自动解析子依赖版本,以锁文件全量快照为准 | 仅锁顶层依赖,子依赖由 registry 解析 |
| 新增依赖必审 | 新增依赖必须说明用途、替代方案和体积影响,禁止为单次任务引入重框架 | 为计算折扣引入整个 symfony/expression-language |
| 升级必分批 | 升级依赖必须分 minor → major 升级,每级跑测试;禁止跨大版本一步升级 | 直接从 PHP 7.4 跳到 8.3 不验证 |
| 漏洞依赖处置 | 发现 CVE 必须在 7 天内升级或替换,未升级前标注为已知风险 | 已知漏洞依赖长期留存 |
交付前逐项确认:
composer.json / package.json 一致latest / * / dev-* 浮动约束| 失败场景 | 回退动作 |
| - | - |
| 锁文件与配置不一致 | 用 composer install / npm ci 重新生成锁文件后提交 |
| 升级后测试失败 | 回退到上一稳定锁文件,分文件分模块排查 |
| 新增依赖体积过大 | 评估轻量替代方案,必要时拒绝引入 |
code-review 进行全面的技术审查(Bug/安全/性能)code-generation 生成功能代码,再用本Skill评估是否符合规范refactoring 获取结构优化方案,再用本Skill检查是否过度设计