# Karpathy编码规范 基于Andrej Karpathy对LLM编码陷阱的观察,提供5条核心编码原则。 ## 输入要求 1. **代码片段**(可选):需要评估的代码 2. **具体问题**(可选):如"这段代码是否过度设计" 3. **场景描述**(可选):编码场景说明 ## 执行流程 ### 第一步:明确目标与假设 - 识别用户真实目标和成功标准 - 标注不确定信息,必要时向用户澄清 - 列出关键假设,不把猜测隐藏在代码里 ### 第二步:应用五大原则 - 用“先证后写”覆盖正确性敏感算法(原则0优先于其余四条) - 用“编码前思考”检查需求歧义 - 用“简洁优先”排除过度设计 - 用“精准修改”限制变更范围 - 用“目标驱动执行”定义验证闭环 ### 第三步:检查代码或方案 - 对照五大原则逐项检查 - 识别具体代码位置、改动点或设计风险 - 区分必须修复和可选优化 ### 第四步:输出改进建议 - 给出最小修改方案 - 提供修改前后对比或明确执行步骤 - 标注保留现状的理由和风险 ### 第五步:验证与沉淀 - 验证建议是否满足原始目标 - 将可复用的编码规范、陷阱和决策记录到项目记忆 ## 五大原则 ### 原则0:先证后写(Prove Before Code) **关键算法先写不变量,再写码。正确性先于简洁。** 编写递归、图遍历、状态机、排序等正确性敏感算法前必须: - 先书面陈述不变量(invariant)、前置条件、后置条件与终止条件 - 再据此写出实现,使不变量在每次递归入口/出口成立 - 用结构化断言验证不变量,而不是依赖"流程跑通" **反模式**: - 直接写环检测递归,边写边改,多次迭代都没写对(缺不变量,测试结果错也看不出来) - 给关键算法只配"正常值"测试,结构边界(自环/互环/平行边)全空 **正确做法**: ``` 环检测不变量: - 前置:图节点集合 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 的执行方法) 原则0 要求"先写不变量再写码",但没讲不变量怎么来。任何图遍历 / 递归 / 状态机 / 排序 / 依赖解析算法,**先按下面三步把不变量显式推出来,再动码**——边界用例表(见 `test-generation.md`)是"授人以鱼",这三步是"授人以渔",P8 面对任意新算法能自己推出不变量,靠的是这个。 **第一步:找循环保持量(Loop Invariant)** > 定义:每次循环 / 递归迭代前后都恒真的命题。 连问三句: 1. 算法"已经正确处理完的部分"是什么?把它写成一句命题; 2. 若算法在任意时刻被强制中断,什么仍然成立? 3. 这个命题在 base case(初始态)为真、且在 induction step(每次迭代后)仍为真吗? **第二步:找终止度量(Termination Variant)** > 定义:每次迭代严格递减、且有下界的量。 问一句:"每次递归,什么量在严格变小?它的下界是什么?" - 环检测示例:未访问节点数严格递减,下界 0 → 必终止。 - 若找不到严格递减且有下界的量,递归可能不终止,必须在动码前先修正设计。 **第三步:找状态守恒(State Conservation)** > 定义:算法维护的多个状态量之间必须满足的守恒关系。 问一句:"哪些量必须守恒?违反守恒就等于出 bug?" - 环检测示例(三条守恒):① 环计数不重不漏;② 遍历方向与边有向性一致;③ 平行边不翻倍。 **推导自检清单(交出草稿不变量后,先自证再写码)**: 1. **可判定性**:能写成可断言的布尔命题,而不是"大概对"; 2. **初始成立**:base case 下命题为真; 3. **归纳保持**:每次迭代后仍为真; 4. **充分性**:不变量 ∧ 终止条件 ⇒ 结果正确。 **落地硬规则**:遇到关键算法,先交出「不变量命题 + 终止度量 + 守恒关系」三件套;**交不出、或过不了自检清单,禁止开始写码,也禁止声称"修复完成"**。 ### 原则1:编码前思考(Think Before Coding) **不要假设。不要隐藏困惑。暴露权衡。** 编码前必须: - 明确陈述假设——如果不确定,提问而不是猜测 - 存在多种理解时,列出选项——不要默默选择 - 存在更简单方案时,说出来——该推回时推回 - 遇到不清楚的地方,停下来——说出困惑所在,提问 **反模式**: - 用户说"导出用户数据",直接写导出所有用户的代码(假设了范围、格式、字段) - 用户说"让搜索更快",直接加缓存和索引(假设了"更快"的含义) **正确做法**: ``` 导出用户数据前,我需要澄清: 1. 范围:导出全部用户还是筛选后的子集? 2. 格式:JSON/CSV/直接API返回? 3. 字段:哪些字段?(有些可能涉及隐私) ``` ### 原则2:简洁优先(Simplicity First) **最少代码解决问题。不要 speculative。** - 不要超出需求的功能 - 不要为单次使用代码做抽象 - 不要未被要求的"灵活性"或"可配置性" - 不要为不可能场景做错误处理 - 写了200行但50行能搞定,重写 **自检问题**:"资深工程师会说这过度复杂吗?"如果是,简化。 **反模式**: - 用户说"计算折扣",生成Strategy模式+抽象类+配置对象(30行设置做简单计算) - 用户说"保存用户偏好",加缓存、验证、合并、通知(没人要求的功能) **正确做法**: ```python # 简单直接 def calculate_discount(amount: float, percent: float) -> float: return amount * (percent / 100) ``` ### 原则3:精准修改(Surgical Changes) **只碰必须碰的。只清理自己制造的。** 编辑现有代码时: - 不要"改进"相邻代码、注释或格式 - 不要重构没坏的东西 - 匹配现有风格,即使你自己会不同做法 - 发现无关死代码,提一下——但不要删 你的修改制造孤儿时: - 删除**你的修改**导致的未使用import/变量/函数 - 不要删除预先存在的死代码,除非被要求 **测试标准**:每一行修改都能追溯到用户的请求。 **反模式**: - 修复空邮箱Bug时,顺便"改进"邮箱验证、加用户名验证、改注释、加docstring - 添加上传日志时,改引号风格、加类型提示、重排空白 **正确做法**: ```diff - if not user_data.get('email'): + email = user_data.get('email', '') + if not email or not email.strip(): raise ValueError("Email required") ``` ### 原则4:目标驱动执行(Goal-Driven Execution) **定义成功标准。循环直到验证。** 把指令式任务转化为可验证目标: - "加验证" → "写无效输入的测试,然后让它们通过" - "修Bug" → "写复现Bug的测试,然后让它通过" - "重构X" → "确保重构前后测试都通过" 多步骤任务时,陈述简要计划: ``` 1. [步骤] → 验证:[检查] 2. [步骤] → 验证:[检查] 3. [步骤] → 验证:[检查] ``` 强成功标准让你独立循环。弱标准("让它工作")需要持续澄清。 ## 失败回退机制 | 失败场景 | 回退目标 | 最大重试 | | - | - | - | | 目标模糊("让它工作"类) | 拆分为可验证子项,附验证方法 | 2 | | 歧义无法澄清 | 列2-3种最可能理解,标注"假设" | 2 | | 需求边界模糊 | 保守保留核心功能,标注"待确认" | 1 | | 测试环境/依赖不可用 | 最小化方式验证核心逻辑(直接调用+断言) | 2 | | 验证失败(外部依赖问题) | 标注"外部依赖异常",记录复现条件 | 1 | | 连续3轮未通过验证 | 暂停循环,产出当前最佳版本+差异报告 | 0 | 不可恢复时输出阻塞清单和升级路径,由用户决策。 ## 输出格式 ``` ## Karpathy编码规范评估 **评估代码**:[文件名/片段] ### 原则0:先证后写 - [ ] 是否涉及正确性敏感算法(递归/图遍历/状态机/排序)? - [ ] 关键算法是否先写不变量、前置/后置/终止条件? - [ ] 是否用结构化断言验证不变量,而非"流程跑通"? **评估**:[通过/需改进/不适用] **问题**:[具体问题] **建议**:[改进建议] ### 原则1:编码前思考 - [ ] 假设是否明确陈述? - [ ] 是否存在未澄清的歧义? - [ ] 是否遗漏了更简单的方案? **评估**:[通过/需改进] **问题**:[具体问题] **建议**:[改进建议] ### 原则2:简洁优先 - [ ] 是否包含超出需求的功能? - [ ] 是否存在单次使用的抽象? - [ ] 是否可以更短更简单? **评估**:[通过/需改进] **问题**:[具体问题] **建议**:[简化方案] ### 原则3:精准修改 - [ ] 是否只修改了必要的代码? - [ ] 是否保持了原有风格? - [ ] 是否清理了自己的孤儿代码? **评估**:[通过/需改进] **问题**:[具体问题] **建议**:[改进建议] ### 原则4:目标驱动执行 - [ ] 是否有明确的验证标准? - [ ] 是否可以独立验证每一步? **评估**:[通过/需改进] **问题**:[具体问题] **建议**:[改进建议] ## 总体评估 **符合度**:[X]/100 **主要问题**:[Top 1-2问题] **优先修复**:[具体建议] ``` ## 反 AI 糟粕代码门禁 生成或审查代码时,以下模式必须作为质量门禁逐项排查: | 门禁项 | 典型表现 | 处理规则 | | - | - | - | | 过度抽象 | 为一次性逻辑创建策略模式、工厂、管理器、配置层 | 删除抽象,回到直接、可读、可测的实现 | | 废注释 | 注释重复代码含义、为显而易见的赋值写解释 | 删除废注释,仅保留解释业务约束、边界条件、危险操作的注释 | | 模板化错误处理 | 每个函数机械包 `try/catch`,catch 内吞异常或只返回空值 | 让异常在合适层级处理;catch 必须记录、转换或给用户可见反馈 | | 防御式臆想 | 为未出现的输入、未要求的插件点、未知未来需求堆分支 | 删除未被需求或测试证明的分支 | | 全文件重写 | 为小修复重排格式、重命名无关变量、搬动无关函数 | 回退到局部修改,只触碰完成目标所需代码 | | 不可验证产物 | 只给代码,不给 lint/test/build/curl/手动验收路径 | 补齐最小验证命令或说明无法验证的具体阻塞 | 交付前必须回答: 1. 新增代码是否直接服务于用户目标? 2. 是否存在可删除而不影响功能的抽象、注释或配置? 3. 是否触碰了与任务无关的文件、函数或格式? 4. 是否有明确验证证据支撑"已完成"? ## 静态分析硬门禁 交付关键算法或修复前,以下静态检查作为硬门禁,必须通过或显式说明豁免理由: | 门禁项 | 工具/动作 | 处理规则 | | - | - | - | | 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` 重新生成锁文件后提交 | | 升级后测试失败 | 回退到上一稳定锁文件,分文件分模块排查 | | 新增依赖体积过大 | 评估轻量替代方案,必要时拒绝引入 | ## 质量标准 - 评估必须基于代码实际内容,不能泛泛而谈 - 每个原则的评估必须有具体的代码位置引用 - 改进建议必须提供具体的修改前后对比 - 不得为符合规范的代码强行找问题 - 评估应平衡:不是追求绝对完美,而是避免常见陷阱 - 必须逐项检查「反 AI 糟粕代码门禁」,发现命中项时优先压缩或删除 - 不能用新增抽象、泛化配置或模板化错误处理替代真正的业务理解 - 关键算法交付前必须通过静态分析门禁(lint/类型/死代码/边界测试),未通过不得声称完成 - 正确性敏感算法必须先写不变量(原则0)再实现,不得边写边猜 ## 关联 reference - **code-review**(代码审查)— 用 `code-review` 进行全面的技术审查(Bug/安全/性能) - **code-generation**(代码生成)— 用 `code-generation` 生成功能代码,再用本Skill评估是否符合规范 - **refactoring**(重构建议)— 用 `refactoring` 获取结构优化方案,再用本Skill检查是否过度设计 - **project-memory-management**(项目记忆管理)— 记录可复用的编码规范、陷阱和决策清单