专题研修

14|代码智能内部:AST、LSP、依赖图与版本化证据

专题研修约 3.2 千字1 组分步图解

目标:将“搜索代码”升级为理解语法、名称绑定与变更影响;同时知道哪些关系无法由静态分析保证。实验:retrieval.symbols和第27章仓库修复。

本章目录

沿着对象与状态理解本章

结构检索比关键词多了一层关系,却仍依赖源码快照与语言解析能力。

下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。

动态图解 · F30

代码检索不能只靠“语义相近”

grep、AST、LSP、调用图各自看到什么?

同一个函数名可能出现在注释、字符串、不同模块的同名函数中。文本搜索先定位线索,语法分析区分结构,语言服务帮助解析具体符号,运行和测试才告诉你这条路径是否真的触发。它们像不同视角的检查,不是越多越好地全部跑一遍。

AST按语言语法组织的代码结构树。
LSP让编辑器查询符号、引用和诊断等信息的协议。
调用图以函数为节点、可能的调用为边的关系图。

输入与适用条件:以foo为教学符号,不指向用户仓库。调用图可能受动态调用、条件编译与运行配置影响。

1 / 6 · 文本搜索

第1步,共6步

窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。

代码检索不能只靠“语义相近”:文本搜索文本搜索找到字符串 foo语法结构AST区分函数定义与注释符号解析LSP确定引用实际绑定哪个定义调用关系查谁调用它、受什么条件控制版本与环境核对当前源码和依赖配置测试与运行用执行证据验证候选原因教学流程;高亮表示当前步骤,不表示真实耗时比例。

搜索命中不代表命中的是实际执行的函数。

本步状态由图形与说明共同给出。

默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。

为什么需要

用代码自身结构减少无关阅读与误定位。

怎样实现

从最便宜且准确的路径/符号检索开始,证据不足再扩大。

可能的代价与误解

一上来全库语义扫描可能漏精确符号,同时浪费上下文。

如何判断是否做对

对已知缺陷记录实际入口、定义、调用者、版本和失败测试是否一致。

1. 文本匹配、语法匹配与语义绑定

全文搜索回答“这个字符串出现在哪里”;AST回答“这个位置是什么语法结构”;名称绑定/类型分析回答“这个标识符实际引用哪个定义”。三者不等价。例如两个文件都有open,它们可能分别是内置函数、方法或导入别名,全文搜索无法消歧。

Tree-sitter查询可以按节点结构和字段定位语法片段;它还暴露错误或缺失节点,适合编辑中不完整代码。LSP定义客户端与语言服务之间的请求,包括定义、类型定义、引用与诊断;具体语言服务器是否支持、精度如何,需要查询能力和实现。[N96][N34]

AST并不自动提供完整调用图。动态属性、反射、依赖注入、生成代码和运行时插件使静态关系可能不完整。本书Python AST实验仅返回函数/类的名称和行范围,不声称完成跨文件名称解析。

2. 一次“技能没有加载”的证据路径

先读取实际任务记录,确定失败发生在发现、选择、读取、注入还是执行。然后定位加载入口和条件分支;再读决定分支的配置类型和调用方;最后找针对这些条件的测试。这个路径围绕一个可证伪假设展开,而不是先遍历整个根目录。

例如候选根因是cwd错误。必须找到启动时cwd如何产生、规则发现函数如何使用cwd、测试是否覆盖嵌套目录。只看到一个函数名load_skills,还不能证明它被调用,更不能证明返回内容进入模型输入。运行轨迹和源码链共同建立证据。

当代码路径不明确,文本搜索可以低成本先找错误消息、配置键和日志事件;语义检索则帮助从“为什么不生效”映射到可能的模块。正确方法通常是组合,而不是宣布某一种检索全面取代其余工具。

3. 文件和符号索引如何切块

按固定字符数切代码,可能截断函数签名、异常处理和闭包。按AST符号切块更符合结构,但大函数仍可能过长,小函数又可能脱离类型与调用上下文。一个合理索引可保存主符号文本、父作用域、导入摘要、路径与版本,再按需扩展关联信息。

符号名称不能单独做全局主键:重载、同名类、不同文件、不同版本都会冲突。可用仓库+版本+路径+限定符号名作为定位,再附稳定范围。行号是快照坐标,修改后可能移动;展示行号时应同时标明版本。

代码注释和文档也是证据,但与执行逻辑冲突时要调查,而不是自动选择更易读的一方。测试断言可以表达预期,源码表达当前行为,两者不一致可能正是待修复缺陷。不能把“当前代码这么写”当成用户期望也必须如此。

4. 依赖图如何用于变更影响分析

图节点可以是模块、函数、类型、配置或测试;边可以表示调用、导入、继承、数据依赖或覆盖关系。不同边的可信度不同:编译器解析的直接引用通常比文本猜测强;运行trace只覆盖实际执行路径,不证明其他路径不存在。

修改函数后,先看直接调用者、参数与返回契约,再看边界使用和错误处理;测试选择可以沿依赖扩展。但静态图缺边、动态路径、共享数据与构建配置都可能导致遗漏。对高风险改动,可在聚焦测试后增加更宽回归,而不是把图当作绝对证明。

这也解释了为什么代码库RAG不仅需要“找最像的一段代码”。实现任务需要定义、调用、配置和测试的组合证据。RepoCoder研究了迭代检索与生成的联动,但仓库补全基准的成功不能直接推成任意Bug修复能力。[N74]

5. Git历史能解释什么

提交历史有助于找到行为何时引入、当时理由、相关测试和回滚风险。它是当前代码理解的辅助,不是替代。某个旧决策可能针对已消失的约束;把旧PR结论直接写成永久规则会造成记忆过期。

对回归问题,可用二分定位引入变化的范围,但环境与依赖必须可复现;否则测试红绿可能来自基础设施噪声。记录失败样本和命令比只记一个commit更有用。提交说明也可能不完整,需要结合diff与可运行证据。

6. “没搜到”为什么不是不存在

搜索可能受忽略规则、文件类型、隐藏目录、索引延迟、语言服务器未完成分析或查询表达式限制。负结论应带搜索范围和方法。例如“在当前src目录中未找到此符号的文本引用”,不能升级成“整个系统不会调用它”。

扩大搜索应由缺口触发:先增加可能模块或配置目录,再查动态注册和生成代码。无限扩展到所有依赖也会降低效率。停止标准是当前证据足以支持决策,或者剩余不确定性已明确且需要外部资料,而非必须遍历所有文件。

7. 检索到证据后还要控制写入

精确定位后,补丁应针对读取版本生成并验证。文件可能在检索与写入之间改变,LSP buffer可能和磁盘不同,测试可能读取构建产物而非当前源码。这些版本错位是AI开发中的高频工程风险。

在IDE场景,应明确以未保存编辑器buffer还是磁盘文件为准;在CLI场景,应检查工作树已有用户修改并避免覆盖。不能因为AI承担开发,就降低普通软件工程中的变更隔离要求。

运行示例 · S14

增量分析一个函数改名,哪些证据应更新

输入与条件。把a.py中的parse改名为parse_config,b.py静态导入它,c.py通过字符串注册名字。

步骤与对象内部动作中间结果 / 新状态
1语法变化a.py定义节点更新增量解析只需重建受影响语法范围。
2静态引用b.py的绑定与调用需更新检查语言服务索引是否重建。
3动态引用c.py字符串未被静态重命名覆盖用注册表与运行测试补验证。
4版本标记旧搜索结果仍显示parse作为过期定位线索,不直接采信。
5最终验证导入、注册查找与调用测试覆盖不同证据类型的遗漏。

把过程连起来。增量语法树更新不等于全项目语义和运行行为自动一致。

边界与反例。各语言服务器重命名覆盖不同;不保证所有动态引用可被静态工具找全。

依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。

8. 验收题

AST找到了一个函数调用,能否证明目标函数必然执行?不能,控制流可能不进入该分支,名称绑定也可能指向其他对象。需要控制流、类型/绑定信息和运行条件。

一次修复的证据包应至少能指出:哪个行为不满足什么要求、根因在哪个具体分支、哪些调用受影响、补丁如何改变行为、当前版本哪些验证通过。内容可简短,但链条不能靠笼统“已全面检查”替代。

记录保存在本机浏览器。

输入关键词即可检索。

↑ ↓ 选择 Enter 打开Esc 关闭 · 仅本地搜索

阅读设置

阅读设置仅保存在本机浏览器,不上传任何数据。