
写代码写了十几年我一直记着一个很老的说法一行代码出来是给两种读者看的一种是编译器另一种是三天后的自己。在编译器还频繁报错的年代这句话很有仪式感。但放到现在我觉得更准确的说法已经变成源码的第一位读者是 AI。这句话不是标题党。你打开一个现代IDE在函数里敲到第三行补全插件就已经把前两行“读”进去了你提交一个分支流水线里的评审机器人会先于团队所有人把 diff 读一遍遇到一个不认识的第三方库很多人的第一反应是把源码丢给 AI让它先从里面提炼出骨架。我最近一年多高强度用 AI 读源码包括 muduo、mybatis、redis 这类经典 C/C/Java 项目也包括嵌入式内核代码最大的体会就是顺序变了以前是“我读一遍写笔记再归档”现在是“AI 先读一遍给我画重点我再按它的线索去读最后复验”。这个转变听起来不大实际引发的连锁反应非常厉害。这篇文章把我这套“AI 先读源码、我再读源码”的流程彻底拆开AI 读取代码的原理是什么它在大型源码库里凭什么能定位关键路径我落地过哪几种工作流以及因为过度信任它翻了哪些车。“源码的第一读者是 AI”不是一句口号它是我验证过的结论而且它反过来还应该改变你写代码的方式。1. 为什么我敢说“第一读者是 AI”三条看得见的证据1.1 从“写完再评审”到“写的过程中它就在看”只要你用带代码补全功能的编辑器AI 把源码读一遍就不是什么悬念而是每次按键都在发生的事。它的工作方式不是“敲一行识别一行”而是先把当前缓冲区里的全部内容作为上下文再预测下一个接续的 token 最可能是什么。也就是说在你按下保存键之前这个模型已经把你当前文件从头到尾“看”过不止一遍。我以前觉得补全就是模板匹配后来了解它的运行机制才意识到凡是自己敲出来的类名、变量名、函数边界都会被模型纳入注意力的视野。它读得比我快几乎每多写一个字符它就对后续空位重新计算一次。你越往下写它掌握的前缀信息越多建议的准确度就越高。这种“第一读者”的身份是真的贴着键盘存在的。1.2 从“人肉评审”到“评审机器人先跑”团队协作场景里证据更明显。前几年我们做 code review 靠人去叫每个人开一次 IDE把 diff 从头往下过一遍看到哪是哪。现在 MR 一挂上流水线先动静态检查先跑接着 AI 评审机器人给出意见哪里缺测试哪里可能出现空指针哪一段命名和现有模块不一致。在这些自动化意见被消化之前人工评审往往排在后半程。整个流程已经把“第一读者”的位置让了出去。这里需要强调一点不是说人不再重要而是说一段源码诞生后将经历的第一次系统化阅读理解发生在自动化侧。尤其当团队同时接入 IDE 补全和 MR 机器人之后AI 已经形成了一道固定的前置阅读岗人是在它之后来做筛选和决策的。1.3 一截源码从诞生到被阅读的前 60 秒把时间线拉出来看会非常直观t0 秒写第一行IDE 开始把缓冲区喂给补全模型t10 秒补全器生成后半段顺手高亮一个类型错误t30 秒保存文件ESLint、Clang-Tidy 这类静态工具开始扫描t35 秒我把刚保存的文件丢给本地 AI 助手让它解释最新改动的影响范围t300 秒推到远端分支CI 里的 AI 评审完成第一轮意见消息弹回桌面。这不是什么未来流程这是我现在每天都在经历的。把这些步骤摊开就是为了说明“第一读者是 AI”已经从修辞层面掉进了工作流层面。你写的每一行本质上都在先接受机器阅读然后才接受人的阅读。2. AI 读源码时到底在读什么2.1 它看到的是 token 流不是“行”和“缩进”很多人以为 AI 像我一样逐行扫代码其实模型看到的是一串 token。拿 Python 举例def calculate_average_price():这一行会被切成一连串子词 token包括空格、缩进、换行符都有自己的 token 表示。这样做的结果是格式差异会真实影响模型的阅读理解质量。比如同样一个函数def calculate_average_price(prices): valid [p for p in prices if p 0] return sum(valid) / len(valid) if valid else 0.0如果全部挤成一行、缩写变量名模型也能读但类型语义和边界轮廓会被削弱。因为换行和缩进本身就是代码结构的信号模型虽然不是人类那种“看缩进就知道作用域”的读者但它能从 token 分布里学到这些信号的统计规律。所以保持良好格式不是讨好代码规范而是在帮 AI 降低误读概率。最近我读那些“源码笔记”类资料时更明显把笔记里的结论和源码良好的缩进结构一起交给模型它的理解准确率高很多。2.2 上下文窗口有限长函数会溢出模型一次能处理的信息量有上限也就是上下文窗口。一个 300 行的 C 函数塞进去之后函数开头和结尾在注意力机制里已经“相隔很远”模型很容易只看到局部就下结论。我实际踩过这个坑。有一次让 AI 排查一个 300 行长函数里的日志错误它把函数里两处结构非常相似的 log 搞混了一口咬定出错的是第一处实际上真正异常的是第二处。后来我把函数拆成几个 60 行以内的小函数再给出同样的问题它的判断一下就准了。这个案例给我的教训是想让 AI 读得准先帮它控制“语境半径”。函数太长等于强迫 AI 在一个有限窗口内去看距离很远的代码它只能择重点而它选的重点未必真是重点。2.3 它不会自动遍历调用图必须靠你喂AI 和人类读者还有一个关键差别它不会自己主动在项目里跳来跳去。你丢给它一个函数体问“这个函数会被哪些场景调用”它如果只看到了函数体而没有收到任何调用方信息就只能在心里打猜。所以喂上下文的时候你需要把调用方线索一起给到它“这个函数被 modules/user/service.go 里的 GetUser 调用入口在 handlers/user.go”它才能顺着线索去分析。人类读代码时可以在文件之间跳转AI 在默认情况下并不会自动获得整个仓库的遍历能力。除非你额外接入了检索系统或者把相关的调用点都贴进提示词否则它对代码的分析一定是局部视角。2.4 命名、类型、注释就是它的路标不同类型的代码信息对 AI 阅读理解的影响权重不太一样。我专门对比过信息类型对人类阅读的影响对 AI 阅读的影响变量名高很高命名差的时候它只能靠位置猜语义类型签名较高很高类型决定推理范围注释里的“为什么”中极高没有它就只能脑补出错误动机格式和换行中高影响 token 分布与结构感知这里面最反直觉的是注释。人会觉得注释啰嗦但 AI 对注释的依赖比人严重得多。比如一段关于客户端消息处理的代码如果有这么一句注释# 这里不能提前释放 buffer底层 socket 回调还要引用它模型读到之后处理生命周期问题时会自动收敛到“释放顺序”这个方向而不是往“内存分配失败”方向瞎猜。你现在再去回看那些“100 个 Python 实战项目附全部源码”之类的仓库会发现好代码几乎都有一个共性在关键地方写出了背后的商业原因或技术约束。那正是 AI 阅读理解所需要的最重要路标。3. 大仓库溯源muduo、mybatis、嵌入式内核源码级别的读法3.1 整包扔进去是灾难有些人拿到 muduo 源码或者一个嵌入式内核源码包的第一反应是把整个仓库复制进 AI 对话窗口然后问“讲讲这个项目的核心设计”。我试过效果很惨要么输出到一半因为上下文超长直接截断要么前面的回答和后面的回答明显矛盾。原因很简单模型没有收到仓库地图它只能在漏进来的片段里连猜带蒙时间一长模块边界和依赖关系在它脑子里就是模糊的。3.2 我会先喂仓库目录地图正确做法是先给 AI 一张“地图”哪怕只是一份只带目录结构的文本。比如读 muduo 时我会上传这样的结构muduo/ ├─ base/ # 原子操作、线程、时间戳 ├─ net/ # EventLoop、Poller、TcpConnection ├─ net/poller/ # epoll/poll 实现 ├─ examples/ # 各种示例常是项目入口 └─ tests/ # 单元测试能告诉你“行为契约”然后再问“EventLoop 的主循环在哪里”模型就能直接定位到 net 目录顺着目录往下分析而不是在所有文件里瞎猜。嵌入式内核源码更依赖这种路径面对drivers/、arch/、kernel/这种庞大的结构我会先把目录列表给它让它判断我应该关注哪一类实现再开始深挖。这套操作看似简单但它能成倍提升 AI 对大型项目的理解和检索效率。3.3 自建一个代码检索系统两条腿走路单个对话窗口喂不下大型仓库不代表 AI 没有近距离接触完整仓库的途径。我在本地做过一套很轻量的代码检索增强方案先把仓库解析成函数级或类级的片段提取出符号表和依赖关系再做向量化索引。提问时先用问题里的关键词召回相关的代码块再把召回结果拼进提示词上下文。流程可以概括为解析源码按函数、类、文件切块对每块生成向量化表示建立索引查询时先在索引里召回相关片段把召回结果拼进上下文让模型汇总出答案。这样模型不是看全局而是看“它需要看的那几片”准确率和成本都可控。这种检索增强阅读方式相当于给 AI 装了一根能定位的指针想读哪块先定位再读取。3.4 实测有效的四类提问模板同样是问源码提问方式差别很大。下面几个是我试下来最容易出有效答案的模板读入口链路“从 main() 出发把消息从 accept 到业务处理函数的调用链列出来标出每个环节所在的文件和行号。”读并发模型“EventLoop 对象在哪些线程里被访问这个项目里线程间同步的边界是什么”读对象生命周期“TcpConnection 从建立到销毁的流程是什么它的析构函数里哪些操作会阻塞”读测试契约“先读 tests 目录用测试用例证明 TcpServer 在异常断连时的期望行为。”这类模板的共同特点是给了起点、给了关注维度、要求了输出格式。AI 接收到越具体的指令返回的结果越接近可用的技术笔记。我还尝试过让 AI 对比 redis 源码中不同版本安装包里的同一实现靠的就是这种“先定位、再对比”的路子效果比直接问“聊聊 redis 的网络模型”要精准得多。4. 让 AI 持续当“第一读者”的三个落地场景4.1 提交前先过一遍它的眼睛提交前审查是我用得最多的场景成本低、见效快。核心动作是把git diff的产物喂给 AI 审查提示让它在东西进入团队之前先做一轮阅读和输出。一个可复制的操作路径git diff --cached --stat git diff --cached /tmp/pre_commit.diff your-llm-client --input /tmp/pre_commit.diff --prompt-file review_prompt.md关键在review_prompt.md里要怎么写。我用的版本大致如下你是一名严格的代码审查者。只基于 diff 中出现的改动做判断。 第一轮列出这次改动的行为影响。 第二轮指出异常分支、资源释放和并发隐患。 第三轮给出最小修改建议。 如果信息不足以判断直接说“这里需要再看 xxx”不要脑补。这套提示词的逻辑是给 AI 分了轮次让它先从行为影响切入再进入细节避免一上来就陷入风格辩论。提交前刷一轮等于在自己代码进入公共分支之前先让第一个读者替你排除掉一批低级错误。4.2 MR 机器人用固定角色和规则压住 AI 的碎嘴MR 评审机器人比提交前审查更重一层。它不只是看 diff还会去看整个合并请求的上下文、涉及模块的历史、是否带了测试。我会在机器人指令里明确角色定位“只做变化审查不评价人的水平不给无依据的建议”。AI 评审容易犯碎嘴的毛病动不动就建议“把这段代码封装一下”这种话对变更本身没有任何审查价值。所以我会给它加一条硬规则如果一条评论不能指出具体文件、具体行号、具体风险就不许发表。这一点非常重要。有了它AI 的第一读者模式才会真正偏向严谨阅读而不是废话轰炸。4.3 自动补全本身就是最持续的“阅读”许多人没意识到IDE 里的代码补全其实是 AI 作为源码第一读者的最常见形态你每敲一个字符模型都会重新读一遍当前文件预测你下一步的意图。一天下来AI 对代码的“阅读量”远大于人工。一个十人团队每天产生的代码变更量几乎全部先经过模型的一次或多轮读取。既然如此想让第一读者读得轻松一点最好保持文件内的语言惯性函数命名风格一致类型注解不跳着写不要在一个文件里交替出现 short style 和 long style。模型在连续阅读同类风格时补全建议的稳定度会明显提升。它读得顺跟着补出来的代码质量自然也就更好。5. 被 AI“读”久了我改掉的五个写码习惯5.1 先把函数的“入口契约”写清楚以前我写代码喜欢从函数体中间开始先写逻辑再补签名的参数名。现在我会先把函数签名和返回类型写完整因为这一小段就是 AI 理解函数的第一入口。比如def handle(data, cfg, conn): ...这种写法对 AI 而言信息太少。改成下面这样之后模型能立刻判断这是什么、不能做什么、适合在哪里被调用def handle_incoming_payload( data: bytes, cfg: AppConfig, conn: Connection, ) - HandleResult: 处理一条来自客户端的原始消息。 - 输入未解包的字节流 - 输出处理结果或错误对象 - 约束不在这里抛异常统一回传错误码 这个习惯改起来其实比想象中难因为需要强制自己先想清楚边界再动手。但从 AI 阅读理解的角度来说收益立竿见影模型拿到签名和文档字符串后分析后续代码时不会再猜“data 是什么编码”。5.2 参数表就是“接口提示词”以前我看到过一个函数参数是(a, b, c, d, e)打开实现才知道那是一个支付确认接口。这种写法对 AI 来说几乎是灾难它只能去看调用点靠“谁在调它”来反推参数含义。到了大模型时代这种模糊信息就被放大成了误报。现在我会把布尔项集中成配置对象把概念上属于一组的数据用一个领域对象替代。参数表不只是给人看的签名它是给 AI 的接口注释。组件边界框得越清楚模型判断“这段代码依赖了什么”就越稳。5.3 注释只写三类意图、约束、反直觉不是所有注释对 AI 都有正向作用。我读完一阵子 AI 生成的路径后把注释收敛成三类意图为什么存在这段代码它解决了什么问题约束哪些事情不能做比如锁顺序、释放时机、平台差异反直觉这里有一个坑正常人第一反应会写错的那种。反直觉类注释尤其重要。比如一个看似多余的判空背后是某个旧版本库曾经返回 None。如果不写AI 很可能会在后续重构里把这个判空当成死代码删掉。写注释时我会提醒自己AI 不知道业务上下文我注释里补的就是它缺失的上下文。5.4 提交信息按照“标题党”原则写提交信息看起来和源码阅读无关实际上对 AI 影响巨大。模型在分析一个变更时经常需要读 commit message 来理解变更动机。以前写“fix bug”“update code”这类信息就等于让第一读者在没有任何背景的情况下硬读 diff。现在我倾向于把提交信息写得像一个标题说清对象、行为和原因。比如修复订单超时取消时重复退款检查#2234 原因取消和支付回调竞态导致退款流程进入两次。 修复在取消入口增加状态机校验只有待支付状态可以进入退款。这种提交信息给 AI 提供了“为什么要改”的锚点它读起 diff 来会非常轻松因为已经从标题里知道了预期的行为变化。与其说我在做代码规范不如说我在做 AI 阅读的“元数据建设”。5.5 魔法值浮出来给数字一个名字代码里的裸数字人类看多了都烦对 AI 来说更是一个猜谜题。if retries 5和if retries MAX_RETRY_COUNT在模型眼里完全不是同一个问题。后者自带语义模型能推断出这里存在重试上限前者则让它需要去整个上下文中找家底有时候还要猜错。所以我现在的习惯是任何有业务含义的数字、字符串、状态值都提成可命名常量。这样既方便人阅读也是在帮 AI 省下无谓的上下文搜索让它更快把注意力放到真正的逻辑上。6. AI 读错的现场以及我兜底的办法6.1 幻觉型误读它可能把因果关系读反AI 读代码也会读错而且错法很有迷惑性。最典型的一种是“因果关系读反”。有一次我让 AI 分析一段重试代码它自信满满地告诉我说“每次失败后立即重试”。但我把完整代码翻出来发现里面明明有一段 sleep并且还做了指数退避。它为什么读错因为模型在有限上下文里把重试逻辑的前半段和后半段割裂了导致它选了一个最顺滑的解释。从那以后我要求 AI 给结论时必须附带“它在哪个文件、哪个行号观察到了支撑证据”。如果给不出行号结论就先标成存疑而不是直接信。这一步看起来简单但对防幻觉非常有效。6.2 上下文过期旧版本里的那个库早已不是它记忆里的样子大型 AI 模型的训练语料有时间截止它记住的可能是某个框架三年前的 API 用法。当我拿一份新版本的第三方库源码去问它时它时不时会把新类名和旧 API 混在一起抛出一些根本不在当前版本里存在的函数。现在的处理方式是提问前先把版本信息写进提示词最前面比如“这是 redis 7.0 的源码不是旧版”并且尽可能附带当前版本的关键头文件或接口定义。把版本锚定了AI 才能收敛在正确的知识区间里。如果它还是拿出不存在的接口那就是提醒我去重新确认文档的真实性。6.3 敏感源码别乱喂私有代码的安全边界要划清楚AI 读源码不是没有代价。业务代码里涉及密钥、鉴权逻辑、核心定价规则这些内容时直接贴进一个公共对话窗口非常不合适。我的原则是这样的能脱敏的先脱敏把变量名、表名、业务参数名替换掉不能脱敏的高敏感源码优先落到本地部署的小模型或者使用企业内部经过审批、数据范围受控的 AI 工具。把源码交给 AI 之前一定要问一句这段代码如果 AI 记住了能不能承担这个泄露风险。这个问题能帮你筛掉绝大多数不适合外传的代码。6.4 我的终审三板斧在一次次的翻车后我给自己定了一个终审流程任何 AI 给出的源码阅读结论都要过三关必须能指出它在哪个文件的哪一行看到的证据必须跑一遍测试套件让测试结果变成第二意见反问一遍“哪里最容易坏”看它会不会在自己限定的上下文里改口纠正。这三步不一定能解决所有误判但能过滤掉一大半幻觉型错误。AI 作为第一读者帮我快速圈定方向我作为第二读者做论证与判断测试套件作为第三方仲裁。这套三角机制比我单纯信任 AI 或者单纯排斥 AI 都稳得多。最后再分享一个小技巧。我现在读任何一份陌生源码都会先让它给我讲一遍然后再让它挑毛病。两个动作合在一起它既做了第一读者也暴露了自己阅读时的盲区。听完之后我再拿盲区去验证往往很快能找到真正的问题点。跟 AI 配合读源码这件事跟带一个学徒很像你说方向、分层次、看结果但真正拍板的人和兜底的人还得是自己。