F30|代码不是普通文档:语法树、调用关系与版本
从具体对象与小例子开始,理解计算过程与适用条件。
本章目录
代码检索不能只靠“语义相近”
grep、AST、LSP、调用图各自看到什么?
同一个函数名可能出现在注释、字符串、不同模块的同名函数中。文本搜索先定位线索,语法分析区分结构,语言服务帮助解析具体符号,运行和测试才告诉你这条路径是否真的触发。它们像不同视角的检查,不是越多越好地全部跑一遍。
输入与适用条件:以foo为教学符号,不指向用户仓库。调用图可能受动态调用、条件编译与运行配置影响。
1 / 6 · 文本搜索
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
搜索命中不代表命中的是实际执行的函数。
本步状态由图形与说明共同给出。默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
用代码自身结构减少无关阅读与误定位。
怎样实现
从最便宜且准确的路径/符号检索开始,证据不足再扩大。
可能的代价与误解
一上来全库语义扫描可能漏精确符号,同时浪费上下文。
如何判断是否做对
对已知缺陷记录实际入口、定义、调用者、版本和失败测试是否一致。
先从一行代码看不同观察层
total = price * count是一串文本,也有结构:左边是赋值目标,右边是乘法表达式,price和count是变量。文本搜索能找到字符,语法分析能识别关系,两者提供的信息不同。
AST(抽象语法树)把代码组织成语法节点,例如赋值节点下面有乘法节点。符号symbol是函数、变量、类型等语言实体;同名字符串可能指向不同符号,所以全文匹配不总能替代语言理解。
LSP不是自动懂业务的模型
LSP(语言服务器协议)让编辑器向语言服务请求“定义在哪、有哪些引用、是否存在类型错误”等能力。它是通信协议,实际分析由对应语言服务完成。
“找到函数定义”与“知道生产环境传进了什么数据”仍不同。类型系统可以排除部分错误,但某个配置导致的运行问题可能需要日志、测试或真实输入。
图、节点和边怎样读
图graph由节点和边组成。函数是节点,调用关系是从调用方到被调用方的有向边。调用图帮助理解影响范围。
DAG(有向无环图)是没有沿箭头绕回自己的依赖图。例如A完成后才能B,A完成后也可C,最后D依赖B和C。它适合安排任务先后,而不是神经网络独有概念。
“依赖闭包”就是沿必要边追到足以解释当前问题的关联;“静态调用图”由源码推断,“动态调用关系”来自实际执行。动态分派、反射和插件会让静态图不完全等同真实路径。
Git的几种状态为什么都重要
commit(提交)固定一份历史快照;分支branch是指向某条提交演进的引用;工作树working tree是当前检出的文件及未提交修改;编辑器里未保存的文本又可能还没进入磁盘。
Agent读取提交A的索引,但修改的是带本地更改的工作树,就可能依据过期内容生成补丁。相同路径不代表相同版本。
diff或patch描述修改前后哪些内容改变。补丁能应用,只说明某种文本或结构匹配成功,不证明业务语义正确。
一次Bug定位的具体顺序
先用失败输入或报错建立可重复现象;定位相关入口与函数;阅读调用者如何传参数;检查配置、版本和边界条件;提出候选原因;用最小实验区分;修改后对当前版本验证。
例如“目录exclude不生效”,只读字符串比较函数可能看不出问题。需要知道路径是否已规范化、目录规则是否应匹配分隔符边界、Windows和Unix分隔是否统一。这些是业务与环境前提,不是模型多读几个随机文件就能自动补足。
“懂业务”需要什么资料
业务对象、核心不变量、术语、外部依赖、异常处理和验收例子,比大量历史闲聊更直接。不变量invariant是在允许操作过程中应始终成立的条件,例如“不能修改公共签名”或“已提交记录必须可追溯”。
这些规则可以存于项目文档和测试中,通过按需检索提供。把全部业务知识塞进一份无限增长的Prompt,会带来检索和维护问题。
编译、测试和运行证据怎样配合
编译检查部分语法与类型约束;单元测试检查特定局部行为;集成测试检查组件配合;生产日志反映实际环境事件。每种证据都有范围,不能用其中一个覆盖全部。
所以代码检索的最终目标不是“找到最多文件”,而是为当前修改获得最小充分、版本正确、可验证的依据。
同一个函数名,文本命中为何不等于真实调用
输入与条件。文件a.py定义 build();b.py有注释“build可能失败”;c.py写 from a import build; build()。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1文本搜索 | 搜索build | 三个文件都可命中,注释也算。 |
| 2语法分析 | 区分函数定义、注释与调用节点 | b.py没有执行build的调用。 |
| 3绑定分析 | 解析c.py的导入关系 | 该调用指向a.py中的build,不是任意同名函数。 |
| 4核对运行入口 | 确认c.py是否在本次任务执行路径上 | 静态有调用也不意味着本次真的运行。 |
| 5查看运行证据 | 测试或日志记录调用发生与版本 | 与静态结构合起来确定问题位置。 |
把过程连起来。grep找字面,AST找结构,语言服务找部分语义绑定,运行证据说明某次实际发生了什么。
边界与反例。这是小型静态Python示例;动态导入、反射、生成代码和不完整项目会增加不确定性。
依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。
课后自检
AST能直接告诉你真实生产数据库返回了什么吗?
查看参考答案收起参考答案
索引基于commit A,工作树已有未提交修改,读到内容一定一致吗?
查看参考答案收起参考答案
延伸阅读:相关系统问题
17|代码智能:从文本搜索到符号、版本与运行证据19|Skills与路由:怎样加载刚好需要的专业能力
20|多Agent:并行的收益与交接的损失
21|可靠执行:重试、幂等、崩溃与状态恢复
22|工程质量:从需求到测试,再到真正可交付的代码
机制依据:[TREESITTER][LSP][SWEBENCH]。本节数值与情景除另注外均为原创教学示例;来源不代替中文讲解。