从专用到通用:Skill 的技术栈中立化改造
一套 skill 的两次生命
最初版本的七个 skill 是为一个项目量身定制的:前端认定 Vue2,组件库认定 element-ui,接口封装认定 api/*.js,数据访问认定 MyBatis 的 XML,数据库认定 PostgreSQL。规则写得又细又顺,产出的文档质量很高,一切美好的前提是——下一个项目长得一模一样。
第二个项目进门时现实揭晓:Python 脚本群加 OCR,没有 Vue;再下一个,ExtJS 加 jqGrid 加 Hibernate,没有 element-ui 也没有 MyBatis。专用 skill 在新栈上开始输出荒唐的结果:给 Python 脚本找 Controller,把 jqGrid 的列当成表格装饰略过不提。
复盘发现所有失效的规则都犯了同一个错误:把「这个项目的做法」写成了「必须这么做」。改造的方向随之确定——把项目习惯从规则里剥离出去,换成技术栈中立的表述,技术栈差异用识别加路由消化。剥离沿着四个维度展开。
维度一:功能点来源
专用版的规则句式是「找 el-button 的 click 事件」。这个句式在 Vue2 项目里精确,出了这个门就是零召回。中立化后的句式改成「找用户的动作入口」,再按栈给出具体形态:
| 形态 | 实例 |
|---|---|
| 模板事件绑定 | Vue 的 @click、JSP 的 onclick |
| 编程式事件绑定 | ExtJS 的 grid.on、button.on |
| 组件定义即功能 | jqGrid 的 colModel、导航栏按钮配置 |
| 后端入口 | Controller 的 @RequestMapping |
| 脚本入口 | Python 主函数、__main__、主流程方法 |
| 时间入口 | @Scheduled、Quartz 任务、crontab |
关键转变是抽象层级:规则描述的是「入口」这个概念,各栈的写法只是概念的实例。模型先理解找什么,再按栈的形态表对号入座。表格里没有的栈,模型也能沿着「用户动作入口」的定义去找,比死记 el-button 泛化得多。
维度二:链路追踪路径
专用版只有一条链:.vue -> api/*.js -> Controller -> Service -> ServiceImpl -> Mapper -> XML -> PG 表。中立化后一条链变成六张模板,覆盖三类 Java 栈、一个纯前端调用、一个 Python 栈、一个定时批处理栈。
模板化时最费琢磨的是粒度。模板太细,每来一个变体就要新增一张,维护不过来;太粗,路由过去等于没路由。最后定下的颗粒度是「数据访问风格」这一层:MyBatis 的 XML 一种,Hibernate 的 DAO 一种,注解 SQL 一种,原生 SQL 一种——这个切分刚好决定链路图的最后三层长什么样。至于 Service 有没有接口、包名怎么起,各项目自由,模板不约束。
另一个统一动作是外部依赖的标注。专用版里 SAP 调用是清账平台的特例,通用版把它升格为一级概念:任何跨出当前代码库的调用都用 External: 前缀统一标注。特例一旦成为概念,第三个项目接进来时 OSS、OCR、第三方接口自动有了位置。
维度三:输出字段来源
输出字段的三级优先级——展示层列定义、SQL 别名、实体属性——这个框架本身是中立的,不中立的是各层在每种栈里的具体形态:
| 优先级层 | RuoYi 栈的形态 | 其他栈的新增形态 |
|---|---|---|
| 展示层列定义 | el-table 的 prop 和 label | ExtJS 的 columns 配置、jqGrid 的 label、返回 JSON 的 key |
| 数据访问层 | MyBatis XML 的 SELECT 别名 | Hibernate 的 HQL 投影、注解 SQL、原生 SQL |
| 实体属性兜底 | Java 实体类字段 | Python 字典 key、表格结构定义 |
改造前最典型的漏案是 jqGrid:colModel 里明晃晃写着每个业务字段的中文名,专用规则不认识它,模型退到 SQL 里硬啃别名。改造后 label 优先,输出字段直接拿到业务命名,列名列序与页面严格一致反而更轻松。这个案例说明泛化不只是兼容更多栈,中立规则的提取本身会让原栈的提取质量变得更好。
维度四:数据库方言
专用版认定 PostgreSQL,注释读写只有一套语法。中立化后按四种方言各配一套:PostgreSQL 的 COMMENT ON、Oracle 的行内注释、MySQL 的建表内 COMMENT、SQL Server 的扩展属性。识别靠系统表结构信号,注释脚本严格按识别出的方言生成。
方言维度还有个容易被忽略的收益:数据字典的三级注释优先级(业务产物、DDL COMMENT、命名直译)是方言无关的,真正随方言变的只有读写语法薄薄一层。把变的和不变的分开,四种方言的成本只增加在语法层,精修逻辑、占位注释识别、待确认兜底全部原样复用。
技术栈识别器
四个维度都指向同一个前提:先知道是什么栈。识别器用三重信号:文件扩展名给出第一判断,目录结构给出交叉验证,框架特征词给出最终确认。三重信号一致才下结论,冲突时报告待人工确认,宁可慢一步,不许猜错栈——栈认错,后面所有模板全部路由错误。
识别的结论写进 function-list JSON 的 技术栈 字段,一次识别全程消费,这是全流水线的固定分工:识别集中在一处,下游全部消费。如果每个 skill 各自识别,同一个项目可能在不同步骤被认成不同栈,中间产物对不上账。
参数与原则的分界
中立化过程中最清醒的一个决定,是区分「做成参数的」和「固化成原则的」:
| 做成参数 | 固化为原则 |
|---|---|
| 模块结构(business.json 按项目配置) | 零合并原则,任何栈不许合并字段 |
| 技术栈模板表(可增删栈) | 先读后写,任何栈不许凭想象写码 |
| 注释方言语法 | 完成标志协议,跨项目统一 |
| 归类关键词(按业务领域调整) | 质量门禁数字与重试降级 |
| 技术底座说明(每项目一份) | 业务语言禁令清单 |
分界线画在「会不会随项目变」上:模块叫什么名字、用哪种数据库,每个项目都不同,这类做成配置,改起来不动规则。零合并、先读后写、N=M 校验这类,是文档工程的底座规律,跨栈跨项目不变,固化在 skill 里谁也不许改。
没有这条分界线的 skill 有两种下场:把参数写死,每个新项目都要改 skill 本体;把原则做成参数,某个项目图省事把零合并关掉,文档质量静默崩塌。分界线让 skill 在「换项目改配置」和「换项目不改底线」之间各得其所。
中立化的代价
通用不是免费的。第一笔账是规则密度:识别表从一张变成七张,链路模板从一张变成六张,skill 文档的篇幅翻了近倍,模型每次执行要处理的指令更长。第二笔账是维护成本:新栈进来要审四维度全部改点,漏改一处就是隐性失败。第三笔账最隐蔽——通用规则的表述天然不如专用规则精确,「找用户动作入口」不如「找 @click」直接,靠形态实例表补回来的精确度,对表格覆盖不到的边角栈是打折的。
这笔账算下来依然划算,因为三个项目的实战证明:定制的成本集中在识别和模板两张表上,是一次性的;而下游占大头的业务翻译、文档组织步骤零改动复用。通用化的收益随项目数线性增长,成本近似常数。
相关阅读:功能点提取一篇有七类入口特征表的完整清单;几百张表、几十个功能点的活怎么拆给一群子代理,见子代理并行的分治实践。