上个月,一位刚转行的同事看我对着 ChatGPT 口述了五分钟,就生成了一整套带 JWT 校验的登录接口,表情复杂地说:“那以后谁还要会写代码?”我回了一句:“谁都要,但要求变了。”他第二天就明白了——新功能做完了,第三天线上高峰出现了一个并发下的 token 过期问题,他对着 AI 生成的那五百行代码翻来覆去找不到状态管理的窗口,最后把我叫过去,我花了十分钟定位到问题,他喃喃地问我:“你是怎么知道要去查 refresh_token 的刷新窗口的?”我意识到,AI 替他写出了代码,但替他长不出代码背后那套“判断力”。这也是我做开源项目 Code to Learn 的真正起点:让 AI 写代码成为学习的入口,而不是学习的终点。
1. AI 代码生成加速了交付,也放大了能力断层
1.1 从“手写能力”到“审查能力”的迁移
不少团队现在已经在把“能正确调用 AI 写代码”作为面试加分项,这没问题。可现实里真正决定项目生死的能力,早就不是“敲出语法正确的代码”了,而是:
- 判断这段代码为什么是这么写的;
- 理解它在极端输入、并发、故障场景下的行为;
- 评估它对现有架构是加分项还是技术债;
- 拆掉需求背后模糊的部分,把它变成可执行的边界条件。
这些能力,AI 生成代码时全部都没有直接给你。你拿到的只是一个“看起来能跑”的结果,但“为什么这样能跑”“什么时候不能跑”这两件事,全都编码进了仓库里,成了你的责任。
我管这种现象叫“能力断层”——工具跑得越快,断层的坑摔得越狠。以前手写代码虽然有成本,但你在写的时候会自发地形成对整体结构和系统约束的感知。现在你用自然语言描述需求,AI 完成从“需求”到“代码”的映射,你反而省掉了中间那段“让思路变成代码”的摩擦,而摩擦恰恰是大脑学习的介质。
1.2 工程能力的构成,不是“写得出”而是“解决得了”
我接触过很多能写出漂亮 demo 的开发者,也见过很多在大型系统里稳住五年不崩的工程师。后者的能力常常被低估,因为它们的观感不直观,例如:
- 面对线上告警,能在十分钟内锁定是缓存穿透还是扇出;
- 在方案评审时,能直接指出新功能与旧模块在数据一致性上的冲突;
- 长期维护一个模块,能预判三个月后哪些地方会因为需求扩展而重构。
这些能力有个共同点:它们都需要你真正“长出”一套跟代码对话的认知模型。AI 可以帮你生成单点函数的实现,但它无法帮你建立这套模型——就像赛车游戏里开辅助线的玩家,去赛道上跑一圈才会发现真正难的是刹车点和过弯速度,而不是方向盘本身。
1.3 一个反直觉的事实:生成得越顺利,积累越少
最让我警觉的时刻,是发现自己已经整周没“写过”一行代码,但功能照常交付了。那一瞬间我并没有兴奋,而是恐惧——因为我的大脑没有及时在负责记忆“今天写了什么代码”的高亮区域里留下任何痕迹。
大脑的记忆机制对“走过场”很不友好。你亲手调试一个 bug,事后会牢牢记住这个错误模式;你直接让 AI 生成二十行代码,三天后问你它用的什么排序算法,恐怕你还要翻记录。
因此,我决定做一个东西,它不阻止 AI 写代码,而是强制你“复盘”AI 写的代码,把你被工具甩开的“学习负荷”重新拉回到你自己身上。这就是 Code to Learn 的雏形。
2. 定位 Code to Learn:它是一套“训练闭环”,不是一个代码生成器
2.1 它是什么,不是什么
很多人听到“开源一个 AI 学习项目”的第一反应是:又一个把 AI 教程包成 App 的东西。其实不是。Code to Learn 是一个基于命令行的学习工作流工具,它把 AI 代码生成的完整过程拆成“需求描述—代码生成—问题质疑—测试验证—重写对比”五个步骤,每一步都强制你从“观众”变成“参与者”。
- 它不是一个写代码的 IDE 插件;
- 它是一个在你身边盯着你“看懂代码”的陪练;
- 它支持常见的编程语言,默认从 Python 和 JavaScript 开始。
它最核心的一条原则是:可以让你借助 AI 快速得到代码,但绝不允许你直接复制代码走人。工具会立刻基于刚才生成的代码,向你提问,要求你解释关键函数的作用、预测异常输入会触发哪一行、甚至要求你徒手补全被挖空的单元测试。
2.2 为什么选择“用 AI 生成,再用 AI 挑战”的设计
你可能要问,为什么不让 AI 直接出题?一开始我确实这么做,但很快就发现了问题:如果提问和回答都由同一个模型完成,学习过程很容易变成“猜模型的口味”。模型太了解自己写出的代码,出的题往往带有强烈的“正确答案暗示”。
所以 Code to Learn 把提示过程拆成了两个角色:
| 角色 | 任务 | 目的 |
|---|---|---|
| 生成引擎 | 根据需求生成代码 | 追求“功能正确”和“覆盖完整” |
| 质疑引擎 | 针对代码提出盲区问题 | 故意制造障碍,检验你的理解 |
| 验证引擎 | 运行测试和 lint,记录你的操作轨迹 | 用客观结果校验你是否真的会 |
三组引擎使用不同的模型实例,甚至故意使用不同的 temperature 和 system prompt。生成引擎鼓励完整性,质疑引擎鼓励刁钻,验证引擎则完全不看你的“解释”,只看代码和测试跑出来的结果。这个分离从第一版开始就定下了,后面所有迭代都是在这个骨架上的补全。
2.3 和“刷题练习”不同的地方在哪里
题库类网站(LeetCode、Exercism)也是很好的学习方式,但它们有一个共同问题:题目是别人设计好的“完美路径”,而真实工程里,你面对的是“模糊需求 + 历史债 + 性能约束 + 团队约定”的混合体。
Code to Learn 的题目不是预设的,而是每次从你“刚刚让 AI 写出来的代码”里动态生成。这意味着它训练的正是你在真实开发中最需要的“解释陌生代码”能力。我在自己的团队里试过,一个初级开发者在连续两周使用这个流程之后,对现有模块的理解速度比以前靠“读源码 + 看文档”要快很多,因为每次 AI 生成的代码都会直接映射到他的业务需求场景里,不需要额外的转换步骤。
3. 端到端实战:从“帮我写个登录接口”到“重构并解释它”
3.1 启动一次 Code to Learn 会话
首先你需要安装工具(后面有完整命令),然后在任意有.py或.js文件的目录里运行:
c2l init my-learning-repo cd my-learning-repo c2l session --goal "实现一个带刷新令牌的登录接口" --lang python它会在你的目录里初始化一个工作区,然后启动一个交互式对话。AI 会先让你补充非功能性需求,比如:“你预期的并发量是多少?令牌存数据库还是 Redis?刷新窗口保持多久?”不回答也没关系,它会给出默认假设,但会在结果里用注释标注。
3.2 生成阶段:让 AI 写代码,但不给你安全感
AI 会把代码生成到一个叫generated/的子目录里。生成完之后,它不会像 ChatGPT 一样弹出一段代码“抄走”。取而代之的是三件事:
- 在终端里逐行高亮关键代码块,高亮的同时提出一个问题;
- 在
questions/文件夹里生成一个 Markdown 问题清单; - 在
tests/里挖掉几个关键断言的实现,要求你补齐。
以登录接口为例,它生成的代码里有一个刷新令牌的方法,但tests/test_refresh.py里有一行被改成了:
def test_refresh_after_expiry(): old_token = create_token(token_id="expired-test", expires_in=-1) response = client.post("/auth/refresh", json={"refresh_token": old_token}) # TODO: 请补充合适的断言 # 提示:思考过期 refresh token 应该返回 401 还是 400?如果你只是想把功能跑通,你很快会碰上一道必须跨过去的槛:你需要理解“refresh token 过期时这个接口的约定行为”,而这一般不在你最初的需求描述里。
3.3 质疑阶段:回答 AI 的反问,问到你说不出话为止
这是 Code to Learn 和普通 AI 编程工具最大的区别。质疑引擎会在生成后轮流向你抛出问题,例如:
- “这段代码里
datetime.utcfromtimestamp()有什么隐患?应该替换成什么?” - “为什么
refresh_token要在数据库里额外存一份哈希,而不是直接返回明文?” - “如果
user_id是一个字符串,这个函数会不会因为类型不同而静默失败?”
这些问题没有任何一个需要“背诵教科书”,全部可以直接在这个代码里找到踪迹。但我发现,大多数初学者连“去找踪迹”这个动作都很难完成——他们习惯了从 AI 那里拿到结果,而不是顺着结果反推设计意图。
你可以在终端里用自然语言回答这些问题,Code to Learn 会记录你的答案。但它不会告诉你对错,而是把答案保存进answer_log/目录。真正的“校验”发生在下一步。
3.4 验证阶段:你补完的测试,会告诉你什么叫会
补完测试之后,运行:
c2l verify --test tests/test_refresh.py工具会把你的测试代码和 AI 生成的实现代码一起跑一遍,给出标准的测试结果(pass/fail)。这里有个隐蔽的设计:如果测试断言写得“过于宽松”(比如任何状态码都算通过),验证引擎会把它识别出来并警告,因为一个不懂实现细节的人很容易写出“永远通过”的测试。
我见过最典型的情况是,学员把断言写成assert response.status_code in (200, 400, 401),理由是“好像都有道理”。Code to Learn 会提示:
检测到断言疑似规避异常状态,请重新思考你的测试目标。这种反馈不是 AI 给的,而是来自技术手段的“系统性质疑”——它的作用就是模拟真实代码评审中,同事看了一眼你的测试然后说“你这测了跟没测一样”的感觉。
3.5 变更需求,让 AI 重写,用 diff 学习重构
你以为学完一遍就完了?还有最后一步最值钱的部分:变更需求,重新生成。
比如上一步的登录接口已经通过了,保持这个版本,然后打开交互窗口,输入新的需求:
c2l session --goal "把刷新令牌改成每次使用后都作废轮换" --ref prev_session它会重新生成一份“轮换模式”的实现代码。这时终端里会出现一个分屏 diff,左边是上一版,右边是这一版。你必须逐行解释:
- 为什么新增了一张
refresh_token_history表? - 为什么旧版本“固定 refresh token”的做法有风险?
- 为什么现在要在 token 表中记录
revoked_at?
这个“从差异中学习”的过程,是我个人认为整个工具里最有价值的部分。因为工程能力的本质,不是记忆 API 调用,而是在“业务规则变化时,能感知到代码的哪一部分会被波及,并做出权衡”。
3.6 一次完整的节奏要花多久
我用这个流程带过几个不同水平的学员,一个简单的功能走完整套,一般耗时 20 到 45 分钟。看起来比直接让 AI 生成代码慢很多,但请注意:直接生成的 5 分钟里,你的大脑基本没有参与任何学习;而这 45 分钟的大部分时间,你都是在和“为什么”搏斗。
一周下来,积累的answer_log/和测试补全记录,会变成一个比你简历更有说服力的能力档案。因为它记录的不是“我做过项目”,而是“我如何理解项目”。
4. 实现细节:如何设计一套“逼你思考”的提示词与测试引擎
4.1 技术选型:为什么用 Python + click + pytest
Code to Learn 的核心逻辑不需要重量级框架,我选择了 Python。理由很简单:
- 生态里有成熟的命令行库
click,维护用户的交互体验很舒服; pytest原生就能动态生成测试报告和断言检查,不需要额外造轮子;- Python 作为大多数使用者“熟悉但不精通”的语言,本身就是一个很好的学习对象。
项目结构大概是:
code-to-learn/ ├── c2l/ # 主包 │ ├── engines/ │ │ ├── generator.py # 生成引擎 │ │ ├── challenger.py # 质疑引擎 │ │ └── validator.py # 验证引擎 │ ├── prompts/ # 各类提示词模板 │ ├── workspace.py # 工作区文件管理 │ └── cli.py # 命令行入口 ├── templates/ # 初始化模板 └── tests/4.2 提示词设计的几个坑
我踩过最大的坑,是以为“让 AI 生成疑问”很简单,实际上直接让模型“提出挑战性问题”的结果是:它自己既当运动员又当裁判,出的题自己都会答,导致你做什么都是对的。解决办法是拆分模型视角。
我采用了三套 system prompt:
- 生成器:
你是资深工程师,写出完整的、带边界的实现,并提供一句使用警告。 - 挑战者:
你不看生成器指令。你只负责分析用户遇到的问题,从边界条件、性能、安全性、可维护性四个维度提问。 - 验证器:
你只负责检查用户的测试代码是否严格、可靠,不对代码功能做解释。
其中“挑战者”的 prompt 里特别强调了一点:“提问时不要直接指出答案,如果你觉得某个边界条件最容易出事,就要求用户先讲出他的想法。”这样就让学习者在脑中完成一次推理,而不是顺着模型的话头点头。
4.3 测试引擎的技巧:绕过“能跑就行”的惰性
验证引擎的核心代码很简单,但有一个很精妙的点:它不只是跑用户的测试,它会额外在背后生成一个“对抗性测试用例”来测试用户的测试。
比如,你的断言是assert response.status_code == 200,那验证引擎会单独把后端改成return {"code": 200, "success": True},但 HTTP 状态码实际是 500,这时你的测试如果仍然通过,就说明你没有真正验证“状态码”,你的测试是无意义的。
这个“测试的测试”机制,让 Code to Learn 能识别出学习者是否在“糊弄”。我见过最离谱的糊弄方式是:为了跑通,用户写了一行assert True。对抗测试直接把它揪了出来。这个机制,也是整个项目里被人点赞最多的一处设计。
4.4 为什么“限制模型聊无关话题”反而更重要
很多人以为学习工具应该开放自由对话,恰恰相反。Code to Learn 的对话窗口里,如果用户问“能不能直接告诉我答案”,默认的回答是拒绝。
但这不意味着冷冰冰。它会给出一个提示:
这个问题的方式不太对,试试换个角度:看看 `generate/` 目录里注释中提到的“刷新窗口”是什么?设计原则是:让 AI 成为一个“引导式导师”,而不是“答案批发商”。只有在用户连续三次尝试后仍然无法突破时,才开放一个“答案提示”的冷却开关。这个开关会记录在日志里,方便以后复盘哪些知识点被你完全卡死了。
5. 开源三个月,我收到的真实反馈和踩过的坑
5.1 用户对“不给答案”的愤怒
项目上线第一周,反对声音比赞同多。很多人打了一星之后说:“这工具是不是有病,AI 都写好了为什么不让我抄?”我发现,这戳中的其实是“工具赋能”和“工具依赖”的核心矛盾。它不像其他代码生成工具那样提供“立即获得感”,所以短期内必然让部分人失望。
但我坚持下来了,因为教育产品本来就不应该以“爽感”为第一目标。我的判断是:这个项目的确不适合所有开发者。如果你是目标明确要“快速交付业务功能”、且团队里有人帮你兜底工程复杂度,那直接让 AI 写代码完全合理;但如果你是个想独立成长的初级工程师,或者带新人的 Tech Lead,那 Code to Learn 的节奏就是一个“必要的不适”。
5.2 技术债:开源社区的贡献者想加功能
第二个坑来自开源社区本身。项目发布后,很快就有贡献者提 PR,想加“直接导出为 PDF 的答案”功能。这跟项目初衷直接冲突——导出答案等于把学习过程外包了。我最终没有合并这个 PR,而是把 discussion 区置顶了一个问题:“如果你只想要答案,你真正需要的是不是一本参考手册?”
这个讨论延伸出一条社区共识:Code to Learn 的目标是“能力测试”,不是“内容交付”。从此之后,我想通了,代码仓库里的功能可以逐步增加,但产品原则不能轻易妥协。为此我把项目的 README 第一句改成了:If you want answer, go elsewhere. If you want understanding, stay.
5.3 被内存和依赖问题干翻过:多模型调用的稳定性格外重要
在技术层面,最多人遇到的是“算了半天没反应”。问题出在生成引擎和质疑引擎同时按顺序调用模型,如果中间有一个超时,整个流程就卡死。后来我改成异步执行 + 轮询进度,每个引擎最多等待 120 秒,超时后允许重新跑。这个经验虽然不 fancy,但对真实使用体验影响极大——很多开源项目就是栽在“功能很酷但跑不动”上。
我也在文档里增加了“不要使用太慢的免费模型”的使用建议,实测下来,只有像 GPT-4o 或 Claude 3.5 Sonnet 这种响应速度够快的模型,才能保证一次会话保持在 5 分钟内的节奏。如果你本地用的量化模型太慢,整体体验会大幅度下降,而且挫败感更强。
5.4 复盘展望:这三个月教会我的东西
开源并不只是发布代码,它更像是一次持续的产品决策。这三个月里,我最大的教训是:工具设计者不能用“我觉得你应该学习”的心态做事,必须提供“学习发生的最小成本”。
于是我在docs/目录里补全了很多“场景化教程”,比如“我有三个小时,想提升 Python 并发能力,该怎么做”。这相当于给使用者一个很薄的启动门槛,让他感受到学习闭环的价值,后面再逐步深入。这种“先给一条窄路,再拓宽”的策略,比一开始就铺满所有功能要好得多。
6. 现在就上手:一面跑通一面构建你的“工程能力档案”
6.1 安装与初始化项目
项目基于 Python 3.10+,安装方式很简单:
pip install code-to-learn初始化一个新的学习工作区:
c2l init ai-engineer-challenge cd ai-engineer-challenge首次运行会检测你的OPENAI_API_KEY或ANTHROPIC_API_KEY环境变量。为了项目许可,我内置了一个最小化模型配置,默认使用 OpenAI 的gpt-4o-mini,足够完成“生成 + 质疑”的基础循环。
6.2 最常用的三条命令
# 开始一个学习会话 c2l session --goal "实现一个限流中间件" --lang python # 运行验证与对抗测试 c2l verify --test tests/test_rate_limit.py # 查看你的能力档案(包含过往的答题记录和测试通过率) c2l profilec2l profile是我后来加上的一个功能。它会把你过去几周里补全的测试、回答过的问题、卡壳过的模块汇总成一份 Markdown 报告。这种报告特别适合用来自我复盘或 demo 时展示——它能直观告诉别人“你在工程思维上做过哪些具体的刻意练习”。
6.3 自定义你的“技能树”
Code to Learn 支持通过c2l init --template frontend切换到前端场景模板。模板里会内置对应的测试环境和提示词,比如对 React 代码进行状态管理能力训练时,质疑引擎会自动聚焦在 hooks 依赖和 re-render 触发条件上。
你自己也能写更加定制化的“问题集”,格式是 YAML,放在custom_challenges/目录下:
- category: "并发" prompt: "如果用户同时用同一个刷新令牌请求两次,会发生什么?" hint: "观察数据库唯一索引" difficulty: 3这类自定义挑战让我在团队内部落地时非常顺手。我直接把之前 code review 里沉淀出的高频问题写成 YAML,然后分给每个新人跑一遍,效果立竿见影。
6.4 从工具到习惯,真正的工程能力是怎么长出来的
开源 Code to Learn 这三个月,我收到最多的感谢邮件,是说它帮他们治好了“代码看了就忘”的习惯。但我也要说句实话:工具再好,如果只是每周末打开一次,效果非常有限。
我建议你把它当作日常开发的一部分:每次你准备从 AI 那里直接复制代码之前,先为这个需求创建一个c2l会话;哪怕只走完“生成—质疑—测试”的其中两步,也比什么都不做更能积累能力。我在实际使用中发现,当我把“看懂 AI 生成的代码”变成肌肉记忆后,我再也不怕 AI 写出来的代码“失控”了——因为我有了足够的上下文去掌控它,而不是被它推着走。
以后无论是换模型、换语言、换业务领域,这个判断力的底子都会跟着你,那才是任何 AI 工具都拿不走的东西。