读源码时,怎么不掉进调用栈黑洞
读 Agent 项目源码,我很容易遇到一种情况:从一个函数跳进另一个函数,再追三四层,眼前这几行好像能看懂,却已经忘了最初为什么进来。
之前看到 session 的缓存与持久化策略时,我就觉得越学越模糊,只好让 Agent 重新串起来,自顶向下讲一遍。个人上下文太小,哈哈哈。
先选一条能走完的主线
README、目录结构和模块关系图,适合用来找入口。但一上来就想把整个项目装进脑子,地图也会变成另一份需要背的材料。
我更想先带着一个问题读,比如“一次请求怎样经过模型和工具,最后得到回答”。据此选出相关入口和核心模块,暂时不展开无关的工具、配置和支撑代码。文件为什么存在、谁跟谁协作,也放到这条线上理解。
下面只是一个简化的 Agent 流程示意,不对应某个项目的真实实现:
run(input)
└─ agent_loop(messages)
├─ call_model(messages) → 最终回答或工具请求
└─ execute_tool(request) → 工具结果写回 messages,再进入下一轮
这张小图先帮我记住两件事:工具结果还要回到循环;循环也得有结束条件。真实项目怎么传数据、在哪里判断结束,需要回到源码确认,不能把示意图当成实现。
进大函数之前,先知道里面在分什么工
一个大函数里又调了很多小函数,直接从第一行开始追,很快就会被某段语法或某个辅助函数带走。
我给自己的提醒是:先让 Agent 讲大体框架,用一句话说清内部每个小函数在干什么,再逐个进去。这次可以把要求收紧一点:
先别逐行讲。沿着这次请求,说明这个函数在主流程哪一步、接收什么、返回什么或改变什么状态;内部调用各用一句话说明职责,并标出定义和调用位置。只展开下一步必须理解的函数,不要凭名字猜实现。
这样再点进去,至少知道自己来找什么。比如进入 execute_tool(),当前要找的是工具请求如何变成结果,以及结果交给谁;某个字符串辅助函数的全部写法,可以另找时间读。
后面才学的概念,先判断它会不会挡住当前理解
分章节讲解有个让我难受的地方:正在学这一章,却撞上后面才解释的知识。立刻补,就不断岔出去;完全跳过,眼前这段又可能没法理解。
这里需要分清两种情况。
如果不懂它,就说不清当前代码为什么走这个分支、数据怎样变化,或者什么时候结束,它就是眼前的前置知识。比如上面的例子里,模型回答和工具请求怎么区分、工具结果怎样进入下一轮,都得先补到能解释当前代码。
如果已经知道它在这里的职责和影响,只是内部细节还不清楚,可以先挂起来。例如本轮只追一条成功完成的请求,日志怎样格式化通常可以后看;但如果日志写入失败会打断请求,这个影响就不能略过。能否暂放,取决于它和当前问题的关系。
挂起也要留线索:这个词用大白话怎么说,在项目里干什么,第一次在哪个文件或调用处遇到,准备在哪一节补。类比能帮助理解就加,不必给每个词都凑一套完整解释。
可以直接告诉 Agent:“这个概念先讲到足够理解当前调用;如果缺了会误解流程,就现在补,否则记入待补项,再回到刚才的位置。”每次讲完一小节,先处理我的疑问,再继续。章节顺序可以调整,不能为了按顺序讲,把必要的语义留空。
读完一段,要能把自己带回来
回到主线后,我想先自己讲:这一段接住了什么,做了哪些处理,结果交给了谁,接下来会发生什么。卡在哪里,就让 Agent 对照源码补哪里,不急着再生成一份更完整的讲义。
一句话职责总结、几句小白版注释,或者补一条图上的关系,够用就好。换一种对象讲解也有价值,但不必为每个模块都准备好几套讲稿。重点是我能不能把刚才拆开的局部重新接起来。
接起来以后,再问设计:为什么把这项职责放在这里?去掉这一层会怎样?有没有别的组织方式?这些问题值得想,但作者没写明的动机,只能先作为推测。
以后再看另一个 Agent 项目,我希望能认出相似的职责和协作关系,再比较实现上的差异。笔记也可以按它们解决的问题归类。记住 EventBus、Provider 这些名字还不够,要知道它们在新项目里是否承担同样的工作。
现在先不追求一套完整的学习体系。下一次读源码,我想先做到:知道为什么往下追,也知道带着什么答案回来。