副标题:不引入复杂解析器,纯用正则扫描几百个源文件,抽出一个近万节点的业务知识图谱。本文复盘它如何从"第一版能用"走到"第三版可移植",以及正则方案的边界在哪。
一、先看问题
有没有一种"低门槛"的办法,把一个中型代码库的业务结构(有哪些模块、哪些功能、哪些数据模型、字段之间怎么关联、界面之间怎么跳转)自动梳理出来,供后续做问答、做检索?
一个务实的答案是:不引入完整的语法解析器,用正则做"够用"的结构抽取。 本文复盘一个真实实践------用纯正则扫描几百个源文件,抽出一个近万节点、上万个关联边的知识图谱,以及它从"能用"到"可移植"的三版迭代。
二、目标与方案选型:为什么用正则
目标很明确:把一个业务模块(几百个 .java / .kt 文件)的领域结构抽出来,形成节点(模块、功能、数据模型、字段、配置模板、外部依赖)和边(属于、依赖、含字段、写入、导航)的图,并落地成可导入图数据库的 JSON 和可读的 Markdown。
方案选型时,面对的是"用 AST(抽象语法树)完整解析"还是"用正则粗抽"的选择:
- AST 方案:准确,但成本高------要为 Java 和 Kotlin 各引入一套解析器,处理大量边界情况,工程量大;
- 正则方案 :不够精确,但够用------业务结构抽取不需要精确到每一条语句,只需要"类级字段 + import 级依赖 + 显式跳转"这个粒度。
最终选了正则。理由很朴素:这个任务要的是"业务结构地图",不是"可编译的语义模型",正则的精度已经足够。
三、三版迭代:每一次都补上一个"正则够不到"的洞
3.1 v1:基础抽取,正则能直接搞定
第一版做的是正则最擅长的部分:
- 字段抽取 :Kotlin 的
data class/ 类体字段,Java 的private字段,用正则匹配成员声明; - 依赖抽取 :通过
import语句判断"谁依赖了谁"; - 领域划分:按包名前缀,把类映射到业务域。
产出约 2760 个节点、5396 条边。这版"能用",但有两个已知局限:局部变量/方法内对象抽不到 (正则只看类级声明),界面跳转关系抽不到(跳转不在 import 里)。
3.2 v2:补"导航边",摸到正则边界
第二版补上了"界面跳转"这个关键维度------因为"业务是怎么串起来的",很大程度体现在"从这个界面能跳到哪个界面"。
用正则匹配显式跳转写法:
kotlin
// 匹配 startActivity(Intent(this, XxxActivity::class.java)) 这类显式跳转
// 源 = 当前文件的首个类,目标 = Intent 里的类
Intent\s*\([^,]*?,\s*(\w+)\s*(?:::class\.java|\.class)\s*\)
这一版抽出了 30 多条导航边。但也暴露了正则的边界:有约 20 多处通过工厂方法(如 XxxActivity.newIntent(this))完成的跳转,正则很难解析------因为它需要"方法名 → 类名"的映射,这不是靠一个正则表达式能搞定的。
3.3 v3:newIntent 解析 + 跨仓库并入,从"能用"到"可移植"
第三版解决了两个问题:
- 补上
newIntent工厂跳转 :用正则匹配(\w+)\.newIntent\s*\(,抽工厂调用并映射到目标类,导航边增加到 47 条; - 并入外部依赖仓库:把另一个相关仓库的源码也纳入扫描,用同一套逻辑抽成"外部类"节点,并建立跨仓库的依赖边。
这一版规模跃升到 9232 节点、14882 边 。更重要的是,它做到了可移植:
- 一键重生成:整个图谱由同一个脚本重跑生成,改了映射规则或正则就能重新产出;
- 路径缺失静默跳过:外部仓库路径不存在时,脚本优雅跳过,不会崩------这保证脚本换一台机器、换一个环境也能跑。
四、正则方案的边界:哪些抽不到,怎么办
实践下来,正则方案有三个明确的边界,需要正视:
- 方法体内的局部对象、局部变量抽不到------正则只看类级声明和 import,看不到方法内部;
- 隐式跳转、反射、动态路由抽不到------凡是"类名以字符串形式出现、运行时才解析"的,正则都无能为力;
- 工厂方法跳转需要额外映射 ------
newIntent这类,要单独写"方法名 → 类名"的规则。
这些边界不是 bug,而是"正则方案"这个选择的固有代价 。应对方式是:用正则做主干,用人工补充或轻量脚本做边角,并明确地在文档里标注"已知限制",而不是假装正则能抽到一切。
五、为什么这个实践值得记
5.1 "够用"比"精确"更重要,前提是明确精度边界
很多场景下,80% 的结构 + 明确的限制说明,比 100% 精确但工程成本翻几倍的方案更有价值。关键是诚实标注"抽到了什么、漏了什么",让使用者知道边界在哪。
5.2 正则抽图的三大支柱
这个实践能成立,靠的是三个清晰的正则维度:类级字段(结构)、import 依赖(关系)、显式跳转(流程)。这三类恰好覆盖了"业务结构地图"最核心的信息。
5.3 可移植性来自"一键重生成 + 优雅降级"
一个抽取脚本是否有价值,很大程度取决于它能不能"换台机器照样跑"。一键重生成(脚本是唯一真相源)+ 路径缺失静默跳过(优雅降级),是让它可移植的关键。
六、可复用的结论
- 业务结构抽取,正则"够用"。 不需要完整 AST,类级字段 + import 依赖 + 显式跳转即可覆盖核心;
- 正视正则边界,并显式标注。 方法内局部对象、隐式跳转、动态路由抽不到,这是固有代价;
- 三版迭代的本质是"补洞":v1 基础、v2 补导航、v3 补工厂跳转 + 跨仓库。每一版都补齐一个正则够不到的缺口;
- 可移植性靠"一键重生成 + 优雅降级"。 脚本是唯一真相源,路径缺失要静默跳过。
写在最后
这个实践最有价值的一点,是它证明了**"够用"的工具 + 清晰的边界意识,能解决很多看起来需要重型方案的问题**。正则抽图不是银弹,但它用极低的成本,换来了一个可用的业务结构地图------剩下的,交给诚实标注的"已知限制"来兜底。
如果你也在做代码结构梳理、知识库建设,先问自己三个问题:要的精度是"可编译"还是"够用"?正则的边界能不能接受?脚本能不能换台机器就跑? 想清楚再选型,往往能省下一大半工程成本。
如果你也用过正则/轻量脚本做代码结构抽取,欢迎在评论区聊聊你的实践。