自研框架有一个很少被提起的隐性成本:它会让 IDE 失明。框架用字符串约定连接世界——Feign 接口里的 URL 和 Controller 方法之间、页面里的多语言 key 和资源文件之间、Mapper 接口和 XML 之间——约定对框架是优雅,对 IDE 是普通字符串:Ctrl+点击无效、改名不联动、引用查找失明。团队每天为这些断链付的时间税,累积起来是天文数字,但因为它是一次一分钟,从来没人立项去修。
我修了:给自研框架写了一个 IDEA 插件,把这些断链全部接通——框架篇讲过的那套”约定优于配置”,在这里补上了它的 IDE 侧半环。本篇讲断链清单怎么盘点、PSI(IDEA 的代码模型)怎么用、两个最有含金量的实现(多语言 key 折叠成中文、URL 引用导航),以及四十四个版本迭代的插件是怎么发布和维护的。工具链线的第一篇(PowerShell 引擎)管进程,这一篇管开发者的编辑器。
一、断链盘点:框架让 IDE 失明的五处
写插件的第一步不是打开 IDEA 的扩展点文档,是盘点断链——团队日常被”跳不过去”卡住的地方在哪。这套框架盘出来五处:
- DAO 接口 → Mapper XML:MyBatis 的经典断链,接口方法到 XML 语句块;
- Feign URL → Controller 方法:两个服务靠一个 URL 字符串相认,改一边另一边静默断;
- 页面 requestUri → Layout XML → Controller:框架页面引擎的三级约定链(框架篇),每一跳都是字符串;
- i18n key → 资源文件:页面上全是
com.xxx.grid.orderno这种 key,看页面代码像看密码; - 字典码 → 字典表:code 的语义只活在人的脑子里。
每处断链对应两种痛:导航痛(想看实现要全局搜索)、重构痛(改 URL 或 key 名没有联动,改漏一处就是线上问题)。给这五处定价,就是给插件立项——它不是炫技项目,是效率债的集中偿还。
二、工程形态:Gradle、版本窗口和私服发布
插件工程的骨架三个决策:构建用 Gradle 加官方 IntelliJ 插件(依赖管理比 DevKit 的手工方式省心一个量级);兼容版本窗口显式声明(sinceBuild 到 untilBuild——IDEA 平台 API 每年大版本都有破坏性变更,窗口声明是”支持到哪、坏在哪个版本”的契约,超窗的用户会收到不兼容提示而不是莫名崩溃);发布走私服——插件没上架官方市场,自建更新描述文件加对象存储分发,团队在 IDE 里配一个插件仓库地址就能收到更新,另配了代码混淆保护内部的框架约定细节。
一个工程细节的教训:插件的兼容窗口每年都要跟着 IDEA 大版本抬一次,**”每年一抬”必须进日历**——平台 API 弃用告警是信号,等正式失效再抬,用户已经在报错了。
三、扩展点地图:每个断链配一个武器
IDEA 的插件体系本质是”扩展点超市”,五处断链各配了一个(或几个)扩展点,这个映射关系是插件开发的入门地图:
| 断链 | 扩展点 | 能力 |
|---|---|---|
| i18n key 显示 | FoldingBuilder(五种文件类型) | 把 key 在编辑器里折叠渲染成中文 |
| i18n key 补全与校验 | CompletionContributor + Annotator | 输入提示、无效 key 标红 |
| URL / Mapper 引用 | PsiReferenceContributor | 把字符串变成可点击的引用 |
| Feign ↔ Controller 双向跳转 | LineMarkerProvider | 侧边栏图标跳转 |
| Mapper XML 结构 | DOM 按根标签挂载 | XML 有类型化的访问模型 |
扩展点选择的原则:导航类断链用 Reference(字符串变引用,获得全部 IDE 原生能力——跳转、查找用法、重命名联动);展示类断链用 Folding(只改渲染不改文件)。选错扩展点的代价巨大——比如用 Annotator 硬做导航,会失去重命名联动,等于只修了半条链。
四、深挖一:把 i18n key 折叠成中文
最有用户价值的实现是折叠:编辑器里的 com.xxx.grid.orderno 直接显示成”计划单号”,文件内容不变——读代码的人看到语义,文件里存的是 key。实现上是 FoldingBuilder 三件套:扫描占位(哪些字符串是 key)、折叠区域计算(渲染替换文本、占位范围)、折叠后的显示文本(按当前语言查资源文件)。
两个实现级的坑值得记。偏移量陷阱:折叠区域和文件实际内容的偏移量计算一旦出错,折叠显示会错位、编辑器光标乱跳——早期版本的折叠偏移问题在页面布局和 Mapper 的特定文件形态下复现,修了一版才稳。性能边界:FoldingBuilder 有”快速阶段”和”完整阶段”的语义区分,快速阶段只允许廉价的文本级判断,把资源文件查找放进去就是编辑器卡顿——这个边界的完整事故在下一展开,这里只立结论:编辑器相关的扩展点,文档里的 quick 语义是红线不是建议。
五、深挖二:把 URL 字符串变成可点击的引用
导航类断链的修复核心是 PsiReferenceContributor——注册”哪类字符串是引用”的规则,IDEA 的 PSI 树(把代码解析成语法树的内存模型)会在光标停留时按规则给字符串挂上引用对象,引用的 resolve 方法负责找到目标元素。挂上引用之后,跳转、查找用法、重命名联动全部免费获得——这是”用平台的原生能力”而不是”自己造个跳转窗口”的差别。
实现上要跨语言:Feign 接口在 Java PSI 里、页面 URI 在 HTML PSI 里、Controller 方法在 Java PSI 里——一个引用规则要在多种文件类型上注册,各自的字符串提取逻辑不同但目标解析共享。双向跳转用 LineMarker 补齐(侧边栏图标):从 Controller 方法一眼看到”哪些 Feign 接口、哪些页面在用我”——反向引用对改接口的人来说比正向导航更重要。
有一类工程问题专门值得一提:多模块同名类。解析 URL 目标时要确定”哪个 Controller”,项目里存在同全限定名类(不同模块各一份)时 resolve 会歧义——后来专门做了模块感知的解析策略。这类”框架约定遇上工程现实”的问题,是插件开发的大部分工作量所在。
六、四十四个版本:CHANGELOG 即产品史
这个插件迭代了四十四个版本,CHANGELOG 连起来读就是一部微型产品史:1.0.3 修折叠偏移(用户报的)、1.0.18 上线 URL 全链路导航(团队最渴的功能)、1.0.28 上线搜索工具窗并修”模块类全限定名重复”……功能来源清一色是团队的真实抱怨,没有一个来自”觉得 cool”。这符合内部工具的产品规律:用户就在隔壁工位,需求即抱怨,迭代即响应——内部工具不必做用户研究,但必须做抱怨的收集器。
维护层面还有一条纪律:翻译生态(集成翻译接口做 key 的一键翻译、四语支持)这类”越界功能”和导航主干分开演进——插件的版本历史证明,克制不住往工具里塞功能的手,CHANGELOG 就会变成杂货铺。
七、面试视角:五个问题拆到底
Q1:为什么值得为自研框架写 IDE 插件?
答:定价——框架的字符串约定让 IDE 失明,团队每人每天若干次的”全局搜索找实现”加”改名改漏”的线上风险,按团队规模年化是可观的效率损失和事故概率。插件一次开发把断链接通,导航、重构联动全部恢复。判断依据:框架的约定越多(URL、key、XML 映射),断链税越重,插件越值。
Q2:PSI 是什么?插件怎么”看懂”代码?
答:PSI 是 IDEA 把代码解析成的语法树内存模型——Java 文件是类方法表达式的树,XML 文件也能挂成类型化的 DOM 模型。插件做的事是往这棵树上”补语义”:注册引用规则让字符串知道”我指向谁”,注册折叠让 key 显示成译文。理解 PSI 的关键是它不是文本处理,是树操作——按节点类型、按结构位置定位,而不是正则匹配文本。
Q3:FoldingBuilder 有什么坑?
答:两个:偏移量计算(折叠区域和文件内容的偏移错位,光标乱跳,要在各种文件形态下测);性能边界(quick 阶段只许廉价文本判断,把资源查找放进去编辑器就卡——这个事故的完整复盘在下一篇)。共同教训:编辑器扩展点的回调频率极高,任何 O(文件大小) 的逻辑都是事故。
Q4:插件的版本兼容怎么管?
答:显式声明兼容窗口(sinceBuild/untilBuild),平台 API 的弃用告警当信号,每年大版本抬一次窗口进日历;发布走自建私服(更新描述文件加对象存储),团队 IDE 配仓库地址自动收更新;内部约定细节用混淆保护。兼容窗口的失守形态是”不声明上限”——平台 API 失效后用户莫名崩溃,比明确提示不兼容差得多。
Q5:功能从哪来?怎么防止工具变杂货铺?
答:功能全部来自团队抱怨(CHANGELOG 四十四个版本逐条可溯源到具体的痛点),不做”觉得 cool”的功能。防杂货铺的机制是主干与生态分离:导航主干严格收敛在五处断链,翻译这类周边能力独立演进、独立开关。内部工具的产品优势是用户在隔壁工位——抱怨即需求,响应即迭代,但抱怨要有收集漏斗,否则做的是提出者自己的需求。
小结
- 自研框架的隐性成本是 IDE 断链税:字符串约定的每一条都是导航痛加重构痛,盘点断链是插件立项的第一步;
- 扩展点选择的原则:导航用 Reference(免费获得跳转/查找/重命名联动),展示用 Folding,选错扩展点只能修半条链;
- Folding 的两大坑:偏移量错位与 quick 阶段性能红线——编辑器扩展点的回调频率是所有逻辑的放大器;
- 引用挂上 PSI 树后,平台能力免费获得:跨语言注册规则、模块感知消解同名歧义是工程量的大头;
- CHANGELOG 即产品史:四十四个版本全部溯源到团队抱怨,内部工具的产品优势是用户在隔壁;
- 兼容窗口必须每年抬、进日历:平台 API 失效的崩溃比明确的”不兼容提示”伤害大得多。
下一篇预告:插件性能事故与代码生成器——一次”打开文件即卡加 Full GC”的事故复盘(foldingBuilder 里查索引的代价)、插件从 1.x 到 2.x 新平台脚手架的迁移,以及一个用反编译取证、golden 快照和 runIde 沙箱验证武装起来的 MES 代码生成器。