采集源码结构
本章要解决的问题
如何从源码中抽取文件、类、方法、候选调用和测试,形成稳定 JSON?
读者读完应获得什么
- 能设计采集输入输出契约。
- 能定义实体稳定 ID。
- 能处理解析失败而不中断全量任务。
本章不讲什么
- 不在本章完成精确语义消解。
- 不支持所有构建系统边角。
采集是流水线第一步。目标是可重复地从 mini-shop 抽出结构事实。
输入输出
输入:
repo_path = examples/mini-shop
source_roots = [src/main/java]
test_roots = [src/test/java]
输出节点:file / class / method / test 输出边:contains / calls(候选) / tests(候选)
扫描与解析:先保证可重跑,再追求完美
- 递归扫描
.java - 区分 main/test
- 解析 AST,保留行号
- 单文件失败时记录错误并继续
第 4 条最容易轻视,但它是“采集器”与“玩具脚本”的分界:真实仓库里总有一些解析不动的文件(生成代码、语法扩展、缺失依赖)。一个文件失败就整体中断的采集器,在 10 个文件的 mini-shop 上没问题,在 10 万文件的仓库上等于不可用。失败隔离意味着:单文件错误进 errors.json,其余照常产出,质量门禁(见后)负责判断这次结果能否进建图。
稳定 ID:身份 ≠ 行号 ≠ 路径
file:pricing/DiscountPolicy.java
class:DiscountPolicy
method:DiscountPolicy#apply
test:PricingServiceTest#shouldApplyVipDiscount
全限定包名与完整路径不进 ID,分别放 qualified_name 与 file_path 属性(与金标 code-graph.json 一致)。简单名在跨仓重名时会冲突,届时在 ID 中补模块前缀(如 method:pricing.DiscountPolicy#apply),并在全仓一次性统一,不要两套并存。
ID 必须在多次采集间稳定,否则影响面与历史分析会断。这里有一个新手常踩的混淆:把路径或行号当 ID。路径在移动文件时变,行号在编辑时漂移——它们都是证据锚点,不是实体身份。影响面要回答“apply 被谁调用”,用的是 method:... 身份;行号只是让证据能回跳到源码的坐标。
如果某天必须改 ID(类重命名),要有显式迁移映射,而不是静默换一个新 ID——否则历史分析、缓存、Agent 记住的符号全都会悄悄断掉。
方法节点样例
{
"id": "method:com.minishop.pricing.PricingService#calculateTotal",
"type": "method",
"name": "calculateTotal",
"file_path": "src/main/java/com/minishop/pricing/PricingService.java",
"start_line": 10,
"end_line": 15,
"calls": [
{"method_name": "apply", "receiver_text": "discountPolicy", "line": 12}
]
}
注意:此时 calls 仍可能是候选,精确绑定可在图谱构建阶段增强。
测试识别
简单规则即可起步:
- 路径在
src/test/java - 类名
*Test - 方法带
@Test
并尝试从测试方法体提取被测调用,生成 tests 候选边。
输出目录建议
out/mini-shop/
files.json
types.json
methods.json
edges.json
errors.json
也可直接合并为 artifacts/code-graph.json 形态。
验收
- 解析全部 mini-shop 源文件
- 包含
DiscountPolicy.apply与OrderService.createOrder - 至少抽到
calculateTotal -> apply候选调用 - 错误文件不影响其他文件结果
采集流程
采集阶段保留候选,符号消解与置信度可在建图阶段提升。
采集是事实链的第一公里。这里的目标不是“解析得尽可能学术正确”,而是稳定产出后续模块能依赖的结构事实:文件、类型、方法、候选调用、测试与 ID。过早做完整语义消解,会让采集变重且难复现;过晚保留候选,又会在建图时丢信息。
工程折中是:采集阶段宁可不完美也要可重跑,并把不确定性留在候选集合里。DiscountPolicy.apply 与 PricingService.calculateTotal 里的调用,应能在输出 JSON 中被找到,即使此时还只是 candidate。
局限
- 候选调用不等于精确调用:采集只负责“挖出来”,语义绑定交给建图。
- 生成代码与非常规目录布局需要配置:采集器不知道你哪条目录是生成的。
- 仅覆盖教学所需 Java 子集时,迁移到其他语言要替换 parser:AST 抽取与语言强相关。
采集与建图的切分:为什么要“留下不确定”
一个常见疑问是:既然建图阶段反正要消解,采集阶段为什么不直接把调用连死?原因是过早消解会让采集器变重、变脆、难复现:
过早消解:采集器要同时理解类型、作用域、框架别名 → 任何一处失败,整个采集不可用
恰当切分:采集只产出候选(receiver_text + method_name + 位置)
→ 建图阶段用符号表与类型信息消解(下一章)
→ 消解不了的原样暴露为 low confidence
凡是后续能补的信息,采集阶段就不必“硬做”。这保证了采集永远可重跑、永远不阻塞——不确定性被安全地留在候选集合里,而不是在静默丢弃中消失。
小结
- 采集先保证覆盖率与稳定 ID。
- 候选调用可以后置消解。
- 失败隔离是工程必备。
- 输出应能直接进入建图。
采集质量门禁
在进入建图前,采集器应输出质量报告:
{
"files_total": 10,
"files_parsed": 10,
"files_failed": 0,
"methods_extracted": 12,
"call_exprs": 8,
"parse_errors": []
}
门禁示例:
- 解析成功率 < 95%:警告
- 关键模块(order/pricing)解析失败:阻断
- 无任何 method 节点:阻断
mini-shop 教学数据应保持 100% 可解析,作为回归基线。
从候选调用到可解释抽取
对 discountPolicy.apply(...),采集阶段至少保留:
- receiver_text
- method_name
- arg_count
- line/column
- enclosing_method_id
这些字段决定后续消解与证据展示是否可用。缺少位置信息的调用边,几乎无法进入 Review 证据层。
关键要点复盘
围绕「采集源码结构」,读者离开本章前应能做到:
- 给出采集输出最小 JSON
- 区分候选调用与已消解调用
- 保持稳定 ID
- 为 mini-shop 列出应抽出的方法集合
- 衔接到建图
若任一做不到,请先复习本章例子与练习,再继续向后读。
采集合约(最小 JSON)
把采集结果落成可机器消费的最小契约:
{
"files": [{"path": "src/main/java/com/minishop/pricing/DiscountPolicy.java"}],
"types": [{"id": "class:DiscountPolicy", "file": "src/main/java/com/minishop/pricing/DiscountPolicy.java", "lines": [3, 10]}],
"methods": [{"id": "method:DiscountPolicy#apply", "owner": "class:DiscountPolicy", "lines": [4, 9]}],
"candidate_calls": [{"from": "method:PricingService#calculateTotal", "name": "apply", "line": 12}],
"tests": [{"id": "test:PricingServiceTest#shouldApplyVipDiscount"}]
}
与前面的“方法节点样例”相比,这个契约站在整包视角:文件、类型、方法、候选调用与测试一次给出。候选调用至少保留 receiver_text、method_name、line、enclosing_method_id,缺少位置信息的调用边几乎无法进入 Review 证据层。
后续建图阶段再把 candidate_calls 提升为带 resolves_to 的 calls 边。采集阶段保留候选,避免过早丢信息。
练习
- 为
DiscountPolicy.apply设计稳定 ID。 - 写解析失败时的错误记录字段。
- 说明候选调用与精确调用的差别,并指出下一章如何消解。
常见问题:采集
候选调用要不要直接当 calls?
不要。先保留候选,消解后再提升。
ID 变了怎么办?
显式迁移映射,禁止静默换 ID。
稳定 ID 与行号是否可变?
ID 不变,行号可变。多次采集间结构位置会漂移,行号只作证据锚点,不充当实体身份;身份靠 method:... 这类稳定 ID。
本章导航
- 上一章:构建一个最小代码理解系统
- 下一章:构建代码图谱
- 相关章:代码图谱:节点、边与属性;Agent 上下文工程
延伸阅读与参考资料
- JavaParser。资料卡:
../docs/research-cards/rc-javaparser.md - Tree-sitter using parsers。资料卡:
../docs/research-cards/rc-tree-sitter.md - JLS
- ANTLR。资料卡:
../docs/research-cards/rc-antlr.md - Source path / build layout conventions (Maven)
- 输出对照:
code-graph.json