想象一下:你用了半年 AI 编程助手,代码写得飞快,但是当你突然遇到 bug,但是一时半会无法使用 AI 的时候,会发生什么样的事情?

前段时间 Anthropic 的研发团队发布了一篇非常有意思的文章 How AI assistance impacts the formation of coding skills ,主要解释了一个问题:AI 写代码能让你更快交付,但会不会让你“学得更慢、会得更少”?

文章说了什么

官方设计了一套随机对照试验,具体实验的过程不过多阐述,可以点开链接看原文.说一下结论.

  1. 使用 AI 变成的程序员在后续测验中平均得分比手动编码组低 17%.
  2. 使用 AI 的程序员完成任务的时间平均快了 2 分钟,但是差异并不算显著;
  3. 在调试、代码阅读和概念理解方面的差距最大;
  4. 最重要的一点:通过提问、请求解释、提出概念性问题等方式主动使用 AI 的程序员,比仅仅用 AI 生成代码的人保留了更多知识。

通过这些结论,推导出了一个很有意思的悖论:“越依赖 AI,越缺乏监督 AI 的能力”.

那接下来,结合这篇文章和我最近一年使用 AI 辅助编程的一些经验,写一些我自己的经验和教训.

一.不要把思考过程完全交给 AI

先回答开始提到的那个问题:AI 写代码能让你更快交付,但会不会让你“学得更慢、会得更少”?

答案是:如果不能正确的使用 AI ,确实会让你“学得更慢、会得更少”.

AI 很容易让我们产生“认知卸载”(Cognitive Offloading)的依赖。一旦习惯了只输入需求、复制粘贴代码,我们的大脑就会停止对代码逻辑边界条件系统设计的深度思考。

在使用 AI 进行辅助编程的时候,要提前思考,先在脑海中或者纸上画出大致的逻辑结构,然后把这个逻辑结构,反复的和 AI 进行讨论和修改.

千万不要让 AI 帮你“设计”一切,而是让它帮你“实现”你的设计,要保证程序员的主体性.

然后当 AI 开始输出代码的时候,要保持 Reviewer 的心态,对待它提交的代码,你必须逐行阅读、理解,并问自己:“如果有 Bug,会在哪里?”、“这里的内存管理安全吗?”。

接着在代码进入调试阶段的时候,要多问自己和 AI,问”为什么”.比如:“为什么要用这个库?”、“有没有更好的替代方案?”、“这段代码在并发场景下安全吗?”。

举个例子

错误模式:
"帮我写个排序算法"

正确模式:
"我需要实现一个稳定排序,数据量大概1万条,内存敏感场景。我先想想...(我的思路),你觉得这个思路有什么问题?如果用快速排序,最坏情况怎么避免?"

二.好好写一份 AGENTS.md

AI 的上下文窗口(Context Window)虽然越来越大,但它依然需要明确的“游戏规则”才能表现得最好。与其每次都重复你的偏好,不如把项目规范固化下来。

AGENTS.md 的作用,主要是可以 明确项目的代码风格(如命名约定、目录结构、使用的库版本,”优先使用组合而非继承”);以及把项目中容易踩的坑、特殊的业务逻辑记录在案,让 AI 在生成代码时能参考,避免重蹈覆辙。

多在子目录写 AGENTS.md

各种 AICoding 往往都内置了一个机制:它读某个目录的文件时,会自动把那个目录的 AGENTS.md 也拉进来。

这实际上就是 LazyLoad 的思路,按需加载:只有真正接触相关代码时才会读对应配置,不会一上来就把所有规则都塞进上下文。

只在主目录中记录最关键的规则

AI 的关注力或者说上下文是非常宝贵的,尤其是对于 AGENTS.md 这种每次都要阅读的文件.

这里最好要小于 100 行,最多最多不要多于 200 行.如果多于这个数字,就要考虑到是不是要拆分规则了.

小心降权和忽略

在上下文过长的时候,有时候 AI 是会偷偷的忽略一些 AGENTS.md 里的内容,以至于有时候会发现在对话过程中,会莫名其妙的少一些明明已经写好的规则.

这里我推荐一个”金丝雀测试法”,我在 AGENTS.md 中,开头加入了一句” 请称呼我为 伯阳大人”,结尾加入了一句”请在每个回答结束后用”喵”结尾”.然后在每个目录下的 AGENTS.md 中单独添加本目录的标识. 一旦发现标识消失了,就要小心了.

对于其它工具的态度

这里多插一句,虽然我也在使用各种 MCPSkills,但是我在大项目中,实际上是尽量减少使用这些的.

因为它们过于复杂和受各种情况影响(网络或者各种奇葩原因),容易叠加风险.反倒是不如细心的维护一套 AGENTS.md 文件,反而更加稳定和安全.

三.当心 AI 一本正经的胡说八道

AI 模型(尤其是 LLM)本质上是基于概率的预测机,它们很容易产生“幻觉”(Hallucination),即自信满满地胡说八道。

前段时间我在研究网络相关的内容,在使用某个刚刚进行了大更新的 AI 时,它就给我输出了一个错的离谱的回答

只能说这个回答确实有点”幽默”了.

对于这种情况,我认为对于 AI 提供的 API 用法或生僻的库,务必去官方文档或源码中核实,不要盲目相信;或者让 AI 解释它的代码逻辑,并要求它提供参考来源。如果它无法解释清楚,或者来源不存在,那很可能是幻觉。

在开头提到的那个实验中,还有一个小结论:Anthropic 的研究发现,AI 辅助下,调试能力和概念理解能力最容易下降。因此,在这两方面要格外警惕,不要让 AI 剥夺了你排查问题的机会。

要对抗 AI

一个人(或一个模型)很容易陷入思维定势。利用 AI 的多角色扮演能力,可以让它自己查漏补缺。

我一般这样做:

  • 开始在讨论程序设计时,让 AI 反问你需求中的漏洞、缺失的异常路径、性能瓶颈;
  • AI 生成代码后,再开启一个新的会话(或让它切换角色),扮演“苛刻的代码审查员”,对刚才生成的代码进行 Review;
  • AI 出题考你,或者让你解释某段代码,然后它来纠正你的理解偏差;
  • 或者直接换一个 AI,对回答进行审查,比如说”这个回答中存在错漏,你找出来”.

这里举个前段时间的例子:上周我用AI写了个 WebSocket 重连逻辑,AI 给的代码看着完美,但我多问了一句’如果服务器返回 401 怎么办?’,AI 才意识到需要处理认证失效场景…”

四.在大型项目中冷静对待 AI Coding

虽然 AICoding 的能力在不断变强,但是我们依然要冷静的使用.

对于一个项目来说,不断的开发是一个熵增的过程.每一个增量都在增加复杂性;尤其是有着复杂的项目依赖和代码风格限制的项目.

这种复杂性非常容易积累,但是却极难消除.这种情况在使用某些使用动态派发的语言(没错,我说的就是 Objective-C),非常容易发生一些奇怪的 bug,并且对于复杂的 API 版本变动(没错,说的就是你, iOS),堪称灾难.

这样就会造成一种结果:每一次变更造成的影响指数型变大,看似简单的变更需要在多个不同层级的地方修改;其次,开发者甚至于 AI 本身都会花费越来越多的时间来掌握上下文.

五.保持自身的学习

很多人低估或者没有意识到 AI 在帮助程序员学习上的能力.

我现在就很喜欢利用 AI 辅助我去阅读源代码,以及各种概念.我主要是进行”摩擦式学习”,反复的和 AI 对话讨论概念,并从不同角度阐述自己的理解,反驳 AI 的结论;或者让 AI 对我进行提问,通过我的回答来验证我的理解.

我们要避免程序员单方向向 AI 提问.要自己向自己提问,还要让 AI 问 AI,乃至AI 向程序员提问.

这里举个例子:我前段时间在阅读 WebKit 源码,我先提问”ResourceLoader::start() 是在什么时机被调用的?它的调用栈通常长什么样?”?然后我阐述一些我自己的观点,然后询问”我说的对不对”,反复执行这个问->答->阐述观点->评判的过程,以便快速的学习.

六.使用 Rust 这种强势编译器语言

我现在在使用 AICoding 的时候,会在可选择的情况下尽量使用 Rust.

首先,Rust 的很多机制,比如说借用检查类型系统生命周期Result / Option 让很多潜在错误在编译期暴露.

同时,编译器能当“高质量的静态审查器”,快速告诉你哪里违反了安全或类型规则。AI 生成的代码往往能跑通逻辑,但容易忽略内存安全或并发安全,Rust 的编译器正好能卡住这些问题。

换句话说,既然 AI 容易导致调试能力下降,那么使用一门“编译通过即大概率正确”的语言,可以减少运行时莫名其妙崩溃的概率,强迫你在编写阶段就理清逻辑。

这种模式下, AI 生成 -> 编译器报错 -> 把错误喂给 AI -> AI 修正。这个循环比“运行 -> 崩溃 -> 猜测 -> 调试”要高效且安全得多。

结论

作为在 AI 浪潮下艰难前行的程序员,我们不应该抗拒时代的大潮,但也不应该放弃思考.

不应该只把 AI 当作“代码代写工具人”,而是当做“解释器+校对员+学习伙伴”,多问”为什么”,少问”怎么做”则可以更加安全高效的使用,以及提升自我的能力.

毕竟自动驾驶确实非常好,但是千万不要忘记握方向盘.