采集源码结构

本章要解决的问题

如何从源码中抽取文件、类、方法、候选调用和测试,形成稳定 JSON?

读者读完应获得什么

  1. 能设计采集输入输出契约。
  2. 能定义实体稳定 ID。
  3. 能处理解析失败而不中断全量任务。

本章不讲什么

  • 不在本章完成精确语义消解。
  • 不支持所有构建系统边角。

采集是流水线第一步。目标是可重复地从 mini-shop 抽出结构事实。

输入输出

输入:

repo_path = examples/mini-shop
source_roots = [src/main/java]
test_roots = [src/test/java]

输出节点:file / class / method / test 输出边:contains / calls(候选) / tests(候选)

扫描与解析:先保证可重跑,再追求完美

  1. 递归扫描 .java
  2. 区分 main/test
  3. 解析 AST,保留行号
  4. 单文件失败时记录错误并继续

第 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 候选调用
  • 错误文件不影响其他文件结果

采集流程

flowchart TD Src[源码树] --> Parse[Parser/AST] Parse --> Extract[抽取 file/class/method] Extract --> Cand[候选调用/测试] Cand --> Out[structure JSON] Out --> Next[交给建图模块]

采集阶段保留候选,符号消解与置信度可在建图阶段提升。

采集是事实链的第一公里。这里的目标不是“解析得尽可能学术正确”,而是稳定产出后续模块能依赖的结构事实:文件、类型、方法、候选调用、测试与 ID。过早做完整语义消解,会让采集变重且难复现;过晚保留候选,又会在建图时丢信息。

工程折中是:采集阶段宁可不完美也要可重跑,并把不确定性留在候选集合里。DiscountPolicy.apply 与 PricingService.calculateTotal 里的调用,应能在输出 JSON 中被找到,即使此时还只是 candidate。

局限

  • 候选调用不等于精确调用:采集只负责“挖出来”,语义绑定交给建图。
  • 生成代码与非常规目录布局需要配置:采集器不知道你哪条目录是生成的。
  • 仅覆盖教学所需 Java 子集时,迁移到其他语言要替换 parser:AST 抽取与语言强相关。

采集与建图的切分:为什么要“留下不确定”

一个常见疑问是:既然建图阶段反正要消解,采集阶段为什么不直接把调用连死?原因是过早消解会让采集器变重、变脆、难复现:

过早消解:采集器要同时理解类型、作用域、框架别名 → 任何一处失败,整个采集不可用

恰当切分:采集只产出候选(receiver_text + method_name + 位置)
         → 建图阶段用符号表与类型信息消解(下一章)
         → 消解不了的原样暴露为 low confidence

凡是后续能补的信息,采集阶段就不必“硬做”。这保证了采集永远可重跑、永远不阻塞——不确定性被安全地留在候选集合里,而不是在静默丢弃中消失。

小结

  1. 采集先保证覆盖率与稳定 ID。
  2. 候选调用可以后置消解。
  3. 失败隔离是工程必备。
  4. 输出应能直接进入建图。

采集质量门禁

在进入建图前,采集器应输出质量报告:

{
 "files_total": 10,
 "files_parsed": 10,
 "files_failed": 0,
 "methods_extracted": 12,
 "call_exprs": 8,
 "parse_errors": []
}

门禁示例:

  1. 解析成功率 < 95%:警告
  2. 关键模块(order/pricing)解析失败:阻断
  3. 无任何 method 节点:阻断

mini-shop 教学数据应保持 100% 可解析,作为回归基线。

从候选调用到可解释抽取

对 discountPolicy.apply(...),采集阶段至少保留:

  • receiver_text
  • method_name
  • arg_count
  • line/column
  • enclosing_method_id

这些字段决定后续消解与证据展示是否可用。缺少位置信息的调用边,几乎无法进入 Review 证据层。

关键要点复盘

围绕「采集源码结构」,读者离开本章前应能做到:

  1. 给出采集输出最小 JSON
  2. 区分候选调用与已消解调用
  3. 保持稳定 ID
  4. 为 mini-shop 列出应抽出的方法集合
  5. 衔接到建图

若任一做不到,请先复习本章例子与练习,再继续向后读。

采集合约(最小 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 边。采集阶段保留候选,避免过早丢信息。

练习

  1. 为 DiscountPolicy.apply 设计稳定 ID。
  2. 写解析失败时的错误记录字段。
  3. 说明候选调用与精确调用的差别,并指出下一章如何消解。

常见问题:采集

候选调用要不要直接当 calls?

不要。先保留候选,消解后再提升。

ID 变了怎么办?

显式迁移映射,禁止静默换 ID。

稳定 ID 与行号是否可变?

ID 不变,行号可变。多次采集间结构位置会漂移,行号只作证据锚点,不充当实体身份;身份靠 method:... 这类稳定 ID。

本章导航

延伸阅读与参考资料

results matching ""

    No results matching ""