你让 AI “加个排序”,它建了 4 个方法;你让 AI “加个时间戳”,它顺手改了 5 处代码。需求没变,产出多了几倍。

Andrej Karpathy 总结了这个问题,做成一套方法论,我做了 4 组对比实验进行验证。


一、Karpathy 观察到的两个问题

Andrej Karpathy(前 Tesla AI 总监、OpenAI 联合创始人)在使用 LLM 编码时观察到两个系统性问题。

  1. 替开发者做决定 — 你要求"加个排序功能",它不问按什么维度排,直接生成 3 个排序方法 + 统一调度层
  2. 过度设计 — 30 行能解决的问题,扩展为 170 行的多层抽象(策略模式、抽象基类、依赖注入)

这两个问题会导致代码偏离原始需求,增加维护成本。


二、Karpathy 的四条原则

Karpathy 把这套约束提炼成了四条原则,你可以把它理解成一个行为模式的 system prompt

  1. Think Before Coding — 不假设,不隐藏困惑,把权衡摆出来
  2. Simplicity First — 最少代码解决问题,不写投机功能
  3. Surgical Changes — 只碰必须改的,不顺手优化
  4. 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,我们沟通讨论。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐