大家好,我是蓝戒,本篇我们来聊聊 ”AI写代码 避坑指南 Karpathy“。
你有没有过这样的体验?让AI帮你写代码,结果它给你整出一套"设计模式全家桶"——你只想算个折扣,它给你来了个策略模式+工厂模式+抽象基类,300行起步,还附赠你没要求的缓存系统和通知模块。
这不是段子,这是千千万万开发者的日常。而前Tesla AI总监、OpenAI创始成员Andrej Karpathy,把LLM写代码的这些"翻车现场"总结成了一组观察,有人将其提炼成了一个精简的配置文件,直接放进项目就能让Claude Code变得靠谱——这个项目叫 andrej-karpathy-skills,目前在GitHub上已经斩获了 45.6K Stars,3.7K Forks。
Karpathy到底说了什么?
事情的起点是Karpathy在X上发的一系列帖子,他精准地描述了用LLM写代码时的那些痛点:
"模型会代表你做出错误假设,然后在没检查的情况下继续跑。它们不管理自己的困惑,不寻求澄清,不揭示不一致,不呈现权衡,该反对的时候不反对。"
"它们真的喜欢把代码和API搞过于复杂,膨胀抽象层,不清理无用的代码……写出超过1000行的臃肿构造,而100行就足够了。"
"它们有时候还是会擅自更改或删除它们不太理解的注释和代码,即使和任务完全无关。"
说得太到位了。用AI写代码,最怕的不是它不会写,而是它"太会写"——写多了、写偏了、写歪了还自己不知道。
四条原则,专治AI的"多动症"
项目作者forrestchang将Karpathy的观察提炼成了四条铁律,写进一个 CLAUDE.md 文件。Claude Code每次启动会话时都会读取这个文件,从此有了"行为准则"。
原则一:编码前先思考
不要假设。不要隐藏困惑。展示权衡。
LLM最大的毛病是什么?是它不会说"我不确定"。面对模糊的需求,它不会停下来问你,而是默默选择一种解释然后一路狂奔。
比如你说"加个用户数据导出功能",LLM可能直接给你写一个200行的完整导出框架,支持JSON、CSV、分页、权限校验——但你其实只需要一个5行的API接口。
正确的做法是:在动手之前,先把假设摆出来——"我理解你需要导出用户数据,请问:导出范围是全部还是筛选?格式是文件下载还是API返回?包含哪些字段?"把模糊点问清楚,比写100行错误代码有价值得多。
原则二:简单优先
解决问题的最小代码。不猜不送的额外功能。
这条原则的精髓在于:不要为"可能的需求"写代码。
看个对比案例。你说"加个折扣计算函数":
❌ LLM的典型输出:
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, amount: float) -> float: pass
class PercentageDiscount(DiscountStrategy):
def __init__(self, percentage: float):
self.percentage = percentage
def calculate(self, amount: float) -> float:
return amount * (self.percentage / 100)
class FixedDiscount(DiscountStrategy):
def __init__(self, fixed_amount: float):
self.fixed_amount = fixed_amount
def calculate(self, amount: float) -> float:
return min(self.fixed_amount, amount)
# ... 还有配置类、计算器类、30行起步的调用代码
✅ 正确的做法:
def calculate_discount(amount: float, percent: float) -> float:
"""Calculate discount amount. percent should be 0-100."""
return amount * (percent / 100)
3行和30行,功能一样。等真正需要多种折扣类型那天,再重构也不迟。
原则二的检验标准特别简单:如果高级工程师说这段代码太复杂了,那就简化它。
原则三:外科手术式变更
只触碰你必须触碰的部分。只清理你自己制造的混乱。
这条原则太扎心了。你说"修一下空邮箱导致崩溃的bug",LLM顺手把邮箱验证逻辑也"优化"了,又把用户名验证加严了,还把注释风格统一了,连引号都从单引号改成了双引号。结果呢?bug修了,但引入了两个新bug。
正确的做法是:改该改的行,一个字都不多碰。 你修空邮箱问题?那就只加空值判断,其他的代码风格、注释格式、变量命名——都不关你的事。
还有一个关键点:如果你注意到有死代码,提一嘴就好,别擅自删除。你删了,别人别的分支还在依赖呢。
原则四:目标驱动执行
定义成功标准。循环直到验证通过。
这是Karpathy最核心的洞察:
"LLM在循环直到达成特定目标这件事上表现得非常出色……不要告诉它具体做什么,给它成功标准,然后看它执行。"
把模糊的"命令式指令"转化为可验证的"声明式目标":
| ❌ 模糊指令 | ✅ 可验证目标 |
|---|---|
| "添加验证" | "为无效输入写测试,然后让它们通过" |
| "修复bug" | "写一个复现该bug的测试,然后让它通过" |
| "重构X" | "确保测试在重构前后都能通过" |
对于多步骤任务,要求LLM在执行前先列计划:
1. [步骤] → 验证: [检查点]
2. [步骤] → 验证: [检查点]
3. [步骤] → 验证: [检查点]
强成功标准让LLM可以独立循环迭代;弱标准("让它跑起来就行")则需要你不停地纠正。
怎么用?两步搞定
方式A:Claude Code插件安装(推荐)
/plugin marketplace add forrestchang/andrej-karpathy-skills
/plugin install andrej-karpathy-skills@karpathy-skills
装完后,这四条原则会作为技能在所有项目中生效。
方式B:手动添加到项目
新项目:
curl -o CLAUDE.md https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md
现有项目(追加):
echo "" >> CLAUDE.md
curl https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md >> CLAUDE.md
你还可以在后面加上自己项目的特殊规则,比如TypeScript严格模式、API必须有测试等。
怎么判断它起效了?
项目README给出了三个信号:
- diff里不相关的改动变少了 —— 只出现你要求的变更
- 因为过度复杂而需要重写的次数变少了 —— 代码第一次就够简单
- 澄清问题出现在实现之前 —— 而不是在出错之后
每一条都在帮你省时间,少踩坑。
写在最后
这套指南的核心哲学是偏向谨慎而非速度。对于简单的任务——比如改个拼写错误、写一行显而易见的代码——你不需要严格执行每一条。但在非琐碎的工作中,多花一分钟思考,远比花一天时间修复一个本可以避免的错误来得划算。
Karpathy说得特别好:LLM不是你的打字员,它是你的队友。而好的队友应该这样工作——想清楚再动手,只做必要的改动,用最简单的方式解决问题,并且每一步都知道什么叫"做完了"。
项目地址:forrestchang/andrej-karpathy-skills,MIT协议,随便用。
文章评论