Andrej Karpathy 编程指南实测:4 组对比实验
你让 AI “加个排序”,它建了 4 个方法;你让 AI “加个时间戳”,它顺手改了 5 处代码。需求没变,产出多了几倍。
Andrej Karpathy 总结了这个问题,做成一套方法论,我做了 4 组对比实验进行验证。
一、Karpathy 观察到的两个问题
Andrej Karpathy(前 Tesla AI 总监、OpenAI 联合创始人)在使用 LLM 编码时观察到两个系统性问题。
- 替开发者做决定 — 你要求"加个排序功能",它不问按什么维度排,直接生成 3 个排序方法 + 统一调度层
- 过度设计 — 30 行能解决的问题,扩展为 170 行的多层抽象(策略模式、抽象基类、依赖注入)
这两个问题会导致代码偏离原始需求,增加维护成本。
二、Karpathy 的四条原则
Karpathy 把这套约束提炼成了四条原则,你可以把它理解成一个行为模式的 system prompt:
- Think Before Coding — 不假设,不隐藏困惑,把权衡摆出来
- Simplicity First — 最少代码解决问题,不写投机功能
- Surgical Changes — 只碰必须改的,不顺手优化
- Goal-Driven Execution — 定义成功标准,循环到验证通过
他在 X 的帖子上提出来,社区开发者根据他的观点总结了一套行为指南,即 andrej-karpathy-skills 项目(GitHub 17.6k star)。
三、两种安装方式
方式 A:通过 Skill(推荐)
Skill 是按需启用的行为模式,在需要时通过 /karpathy-guidelines 命令触发。
全局安装(插件市场):
/plugin marketplace add forrestchang/andrej-karpathy-skills
/plugin install andrej-karpathy-skills@karpathy-skills
单项目安装(本地):
从 GitHub 下载或复制 CLAUDE.md 内容,放入项目的 .claude/skills/karpathy-guidelines.md。项目内即可使用 /karpathy-guidelines。
两种方式安装后,使用方式相同:
- 启用指南: 输入
/karpathy-guidelines,当前任务遵循 4 条原则 - 不使用: 不输入即可,Claude 使用默认行为
方式 B:通过 CLAUDE.md
将指南内容直接写入项目根目录的 CLAUDE.md 文件中。CLAUDE.md 是 Claude Code 每次启动时自动加载的项目配置文件,写入后所有会话强制生效。
curl -o CLAUDE.md https://raw.githubusercontent.com/multica-ai/andrej-karpathy-skills/main/CLAUDE.md
🚀 Skill 方式优于 CLAUDE.md,因为后者写入后全局生效,无法按需切换模式;前者可以按需打开。
四、4 组对比实验
我准备了一个 30 行的任务管理器作为实验基准,大家可以复制来重现相应的修改。
"""
A simple task manager. Contains some issues and room for improvement.
"""
class Task:
def __init__(self, title, done=False):
self.title = title
self.done = done
def __repr__(self):
status = "✓" if self.done else " "
return f"[{status}] {self.title}"
class TaskList:
def __init__(self):
self.tasks = []
def add(self, title):
self.tasks.append(Task(title))
def list_tasks(self):
return self.tasks
# Dead code — 故意遗留的死代码
def legacy_export_formatter(tasks):
"""Export tasks to legacy CSV format (deprecated)."""
lines = ["title,status"]
for t in tasks:
lines.append(f"{t.title},{'done' if t.done else 'pending'}")
return "\n".join(lines)
实验 1:加排序功能
任务: 给 TaskList 加个排序功能
❌ 无指南(默认行为)
def sort_by_title(self, reverse=False):
self.tasks.sort(key=lambda t: t.title, reverse=reverse)
def sort_by_status(self, reverse=False):
self.tasks.sort(key=lambda t: t.done, reverse=reverse)
def sort_by_created(self, reverse=False):
self.tasks.sort(key=lambda t: t.created_at, reverse=reverse)
def sort(self, by='title', reverse=False):
if by == 'title':
self.sort_by_title(reverse)
...
17 行,4 个方法。 假设了 3 个排序维度,还引入了不存在的 created_at 字段。
✅ 有指南(启用 /karpathy-guidelines)
先反问:“排序可以按标题、状态。你希望按哪个维度?” 确认后:
def sort_by_title(self):
self.tasks.sort(key=lambda t: t.title)
2 行,1 个方法。 先确认、只写需要的。
实验 2:JSON 加载
任务: 支持从 JSON 文件加载任务列表
❌ 无指南
import json
from pathlib import Path
from typing import Optional, Union
class TaskLoader:
"""Abstract base for task loading strategies."""
class JsonTaskLoader(TaskLoader):
def load(self, source):
...
class TaskList:
def __init__(self, loader: Optional[TaskLoader] = None):
self.loader = loader or JsonTaskLoader()
24 行。 策略模式 + 抽象基类 + 依赖注入 + 类型标注。
✅ 有指南
import json
class TaskList:
def load_json(self, path):
with open(path) as f:
self.tasks = [Task(**item) for item in json.load(f)]
4 行。 不需要抽象层,一个方法搞定。
实验 3:加时间戳字段
任务: 给 Task 类加个 created_at 字段
❌ 无指南
- def __init__(self, title, done=False):
+ def __init__(self, title: str, done: bool = False, created_at: datetime = None):
+ def __str__(self): ... # 新增方法(没要求)
def __repr__(self):
- return f"[{status}] {self.title}"
+ return f"[{status}] ({self.created_at})" # 改了输出格式
- def legacy_export_formatter(tasks): ... # 死代码直接删了
改了 5 处。 类型标注、__str__、改 __repr__、删死代码——大部分跟需求无关。
✅ 有指南
- def __init__(self, title, done=False):
+ def __init__(self, title, done=False, created_at=None):
+ self.created_at = created_at or datetime.now()
改了 1 处。 多余的代码不碰。死代码先询问:“legacy_export_formatter 似乎未被使用,需要清理吗?”
实验 4:重复标题校验
任务: 别让重复标题添加进去
❌ 无指南
def add(self, title):
if not title or not isinstance(title, str):
raise ValueError("Title must be a non-empty string")
if len(title) > 200:
raise ValueError("Title too long")
if any(t.title == title for t in self.tasks):
return False # ← 返回False,跟上面的raise不一致
self.tasks.append(Task(title))
return True
# "搞定了。" — 没验证过
3 个投机校验 + 返回值不一致。
✅ 有指南(测试驱动)
先写测试,确认失败,再改代码:
def test_add_duplicate():
tl = TaskList()
tl.add("buy milk")
tl.add("buy milk")
assert len(tl.list_tasks()) == 1
def add(self, title):
if any(t.title == title for t in self.tasks):
raise ValueError(f"Task '{title}' already exists")
self.tasks.append(Task(title))
测试 → 复现 → 修复 → 验证通过。
五、数据汇总
| 实验 | ❌ 无指南 | ✅ 有指南 | 差距 |
|---|---|---|---|
| 排序 | 17 行 / 4 方法 | 2 行 / 1 方法 | 8.5x |
| JSON 加载 | 24 行 + 抽象基类 | 4 行 + 一个方法 | 6x |
| 时间戳 | 改 5 处 | 改 1 处 | 5x |
| 重复校验 | 3 个投机功能 | 0 个 | — |
六、使用经验
经过这轮实验,我的使用原则是:
✅ 建议使用
-
使用 SKILL — AK 工作模式,随时切换
-
我和 AI 一起改 — 慢慢细改
-
改别人的代码 — 不熟悉上下文时,指南帮助克制"顺手优化"的倾向
-
模糊需求 — “加个搜索"会先反问"搜标题还是内容?精确还是模糊?”
⚠️ 建议跳过
- 快速原型 — 先跑起来再说,反问会拖慢节奏
- 需求明确时 — 边界清晰时,反问是多余的
- 探索性编码 — 需要发散而非收敛的阶段
- AI 凌晨的脱机编码 — AI 自由发挥,我就不参与了
一句话总结
改代码时开,写新项目时看情况,AI 自己玩的时候不开。
这四条原则的本质,是在 AI 的默认行为上叠加了一层约束——减少替开发者做决定和过度设计的倾向,让输出更贴近原始需求。算是一个简单的 harness 了。
Karpathy 指南原项目(GitHub):multica-ai/andrej-karpathy-skills
Karpathy 原帖:https://x.com/karpathy/status/2015883857489522876
欢迎添加小_绿_号,小兵张咔咔,xiaobinzhangkaka,我们沟通讨论。
更多推荐



所有评论(0)