ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

代码生成器开发指南:从元数据到Java模板的工程实践

代码生成器开发指南:从元数据到Java模板的工程实践 1. 先想清楚一个问题普通的复制粘贴到底输在哪代码生成器这个东西市面上现成的一大把但真正适合自己团队的大概率还是要自己写。这篇开发指南就是我从零搭建、并在三个项目里迭代过一套生成器的完整复盘——里面没有理论堆砌全是已经在生产环境里跑过的方案、模板和踩出来的坑。先说我为什么开始碰这个东西。大多数项目起步阶段CRUD 代码全靠复制粘贴从旧模块复制一个 Controller改改类名、换换字段再复制一个 Service、一个 Mapper XML。前两个模块这么做没问题等模块涨到二十来个的时候隐患就开始冒头了。最典型的一次事故客户要求把某个业务字段从 String 改成 Long我按全局搜索把涉及的文件逐个点开改改了四十多分钟以为自己全改完了结果编译一跑还有三处漏网之鱼其中一处是 mapper XML 里的 parameterType。那一刻我意识到复制粘贴维护的不是代码是一堆定时炸弹。字段映射、异常处理风格、注释格式每一个不一致的点都可能在后续迭代里咬你一口。与其继续手工复制不如花几天时间把复制粘贴这个动作本身固化成工具——这就是我写生成器的起点。1.1 现成生成器为什么总差那口气先别急着动手写市面上的生成器确实不少老牌的 MyBatis Generator、偏重全栈脚手架的 JHipster、各种配套的 renren-generator 之类。这些工具都用过一轮之后我最大的感受是它们解决的是通用场景而你实际遇到的是带团队私有约定的场景。举几个具体差异你就明白了。我们团队有一个统一的 BaseEntity每个实体必须继承它里面包含 createTime、updateTime、deleted 这些公共字段所有接口返回必须走统一响应体RT错误码有自己的枚举ServiceImpl 里必须加一个自定义注解用于记录操作日志。这些约定MyBatis Generator 不会替你生成JHipster 生成的代码风格和我们团队完全不是一个路子。通用工具也不是不能定制很多支持自定义模板。但问题是定制一次的成本不低而且工具版本升级之后模板 API 可能要跟着改第三方项目的维护节奏你控制不了。等它支持新特性你的脚手架可能早就等不及了。所以我的结论很直接如果团队有稳定的代码规范、有足够的重复代码场景自己写一个生成器前期投入三五天后续省下来的时间和一致性收益是通用工具给不了的。1.2 生成器真正解决的问题是固化约定很多人把生成器理解成自动复制粘贴这个认知会误导架构设计。复制粘贴只是生成器最外层的功能它真正的价值在于把团队约定变成可执行、可校验、不会腐烂的代码。我这么解释你就能体会了。假设团队规定所有新增接口必须写 Swagger 注解、必须做参数校验、必须捕获 BizException。手工开发时这一步靠 code review 盯靠新人培训灌输总有人漏。但如果你把这些规则写进生成模板里那么从生成器出来的每一份代码天然带着注解、带着校验、带着异常处理。Review 的人只需要看业务逻辑不需要检查那些重复性的格式问题。还有一个被低估的价值一致性。字段类型调整、统一加审计字段、全局改日志框架这类跨模块的需求在手工模式下意味着几十个文件的手术。有了生成器很多时候只需要改一份元数据重新生成一遍再 review 一下 diff 就行。代码风格的一致性也上来了——生成器产出的代码缩进、命名、注释风格统一新人接手任意模块都不会有陌生感。1.3 不是所有项目都适合上生成器灌完这碗鸡汤也得泼盆冷水。生成器不是银弹我见过不合适的场景硬上生成器最后维护生成器本身的时间比写业务代码还多。什么情况不适合上第一业务模块之间差异性极大每个模块的核心逻辑都不同公共部分只有包名和类名这种程度那生成器省不了多少事反而因为多了一层元数据要维护净成本是负的。第二架构还在剧烈变动期。比如你连 Controller 层要不要统一返回RT都还没定下来建议先别写生成器——生成器最怕的就是元数据稳定而模板不稳定架构变一次模板要跟着改一次两三次下来你就烦了。什么情况适合上重复性 CRUD 多、模块结构相似、团队有一套已经跑稳的代码规范至少满足前两条再加第三条。我写过的那套生成器就是在一个后台管理系统里跑起来的那类系统四十多个页面接口长得几乎一模一样简直是生成器的天然试验田。2. 生成器的四层骨架元数据、模板、引擎、输出想清楚为什么写之后来看怎么写。我的经验是一个合格的代码生成器无论是什么语言、什么实现方式核心骨架都是四层元数据模型、模板文件、生成引擎、输出策略。把这四层的职责边界划清楚后面开发起来会非常顺边界模糊则必然踩坑。2.1 元数据模型所有文件的源头元数据就是你要生成什么的结构化描述。拿一张数据库表举例元数据至少要包含表名、类名、包名、表注释、字段列表。每个字段又包含列名、属性名、Java 类型、JDBC 类型、注释、是否是主键、是否允许为空。我习惯先在代码里把这些定义成纯数据结构不掺杂任何生成逻辑。Java 项目里很简单public class TableMeta { private String tableName; // 数据库表名 private String className; // 生成的实体类名 private String packageName; // 包名 private String comment; // 表注释 private ListColumnMeta columns; // getter / setter 省略 } public class ColumnMeta { private String columnName; // 数据库列名 private String fieldName; // 实体属性名 private String javaType; // Java 类型 private String jdbcType; // JDBC 类型 private String comment; // 字段注释 private boolean primaryKey; // 是否主键 private boolean nullable; // 是否允许为空 }元数据本身怎么来通常有三条路手写 JSON 描述、读数据库表结构自动生成、用已有的实体类反射生成。我建议第一版先用手写 JSON因为最直观、最容易调试。后面表多了再考虑数据库反读这一点到进阶方向那节细说。这里有一条重要原则元数据是整个生成过程唯一的输入契约所有模板都必须围绕它的结构来写。所以元数据的结构一旦定了就要尽量保持稳定——你可以在字段上做加法加新的属性但不要轻易改字段的名字和含义否则所有模板都要跟着动。2.2 模板引擎怎么选模板引擎是生成器的核心执行器选型直接决定你后续写模板的幸福感。我把常见的选择列个表引擎语言生态特点适合场景FreeMarkerJava成熟稳定内置指令丰富IDE 插件完善错误信息带模板行号Java 后端团队首选VelocityJava老牌但迭代慢语法略显陈旧维护老项目时才会碰Jinja2Python语法简洁模板继承能力好Python 工具链或做独立 CLI 工具EJS / NunjucksNode.js与前端生态贴合生成前端代码为主的场景手写 StringBuilder任意无模板概念逻辑全在代码里只生成长度极短的固定片段不推荐用于复杂生成我自己的选择是 Java FreeMarker原因很实际后端团队本来就是 Java生成器作为一个小工具直接并入工程的 tools 模块大家维护起来没有额外语言负担。FreeMarker 的?cap_first、#list、#if这些内置能力写代码模板时足够顺手而且它的报错信息会指出模板的第几行出了问题排障比某些引擎友好得多。需要特别提醒的是别想着自己拼字符串当模板。第一版我犯过这个错用String.format拼一段实体代码字段一多、逻辑一复杂拼接代码比业务代码还难维护转义、缩进、循环全部变成手写逻辑bug 多到怀疑人生。模板引擎这种成熟组件能用现成的就别重复造轮子。2.3 生成引擎与输出策略生成引擎本身不复杂职责就四步加载元数据、准备 FreeMarker 环境、遍历模板渲染、把结果写到目标路径。难的不是渲染是输出这一步——文件写到哪、怎么处理已存在的文件。输出目录结构我建议直接对标目标工程的包路径output/ └── com/example/demo/ ├── controller/SysUserController.java ├── service/SysUserService.java ├── mapper/SysUserMapper.java ├── entity/SysUserEntity.java └── resources/mapper/SysUserMapper.xml输出策略是新手最容易忽略的环节。我第一版生成器就是无脑覆盖后来出了事故才认认真真把策略分成四种覆盖OVERWRITE目标文件存在也直接写。适合纯生成文件且你的元数据是唯一事实来源的场景。用了覆盖策略就必须配合 git每次生成后看 diff 再提交。跳过SKIP文件已存在就不动。适合首次生成脚手架、后续手工演进的场景但生成的代码会慢慢和元数据脱节。备份BACKUP覆盖前把旧文件复制一份带时间戳的 .bak。这是我会在风险文件上用的保险丝。合并MERGE通过特殊标记保留文件中用户自定义的片段其余部分重新生成。这个实现成本最高通常只有 MyBatis Generator 那种复杂工具才需要我至今没在自己项目里用上。核心原则一句话默认走覆盖加 git diff 审核遇到不想被覆盖的文件单独标记为跳过。3. 搭一个最小可用版本Java FreeMarker 实战概念说再多不如直接跑一个最小版本。这一节是照着我的第一版抄作业工程结构、模板、主流程都给你跑通了再往里面加你的团队约定。3.1 定义元数据文件和模板文件第一步先建一个生成器自己的工程目录我把它放在主工程的tools/code-generator下面和业务代码隔离。Maven 里引入三个依赖就够了FreeMarker、Jackson读 JSON 元数据、Lombok可选省 getter/setter 体力活。然后在src/main/resources/templates下面建模板文件夹meta 信息先写成 JSON 文件放在 generator 的配置目录里{ tables: [ { tableName: sys_user, className: SysUser, packageName: com.example.demo, comment: 系统用户表, columns: [ { columnName: id, fieldName: id, javaType: Long, comment: 主键, primaryKey: true }, { columnName: user_name, fieldName: userName, javaType: String, comment: 用户名 }, { columnName: created_at, fieldName: createdAt, javaType: LocalDateTime, comment: 创建时间 } ] } ] }注意这里有个小小的约定数据库下划线列名user_name我在元数据里已经直接转成了驼峰属性名userName。这个转换也可以放在代码里做但第一版我建议直接手写在 JSON 里让元数据文件和模板都保持可读。实体类模板长这样package ${meta.packageName}.entity; import java.time.LocalDateTime; /** * ${meta.comment} * 本文件由代码生成器生成如需修改请先修改元数据定义后重新生成。 */ public class ${meta.className}Entity { #list meta.columns as column /** ${column.comment} */ private ${column.javaType} ${column.fieldName}; /#list #list meta.columns as column public ${column.javaType} get${column.fieldName?cap_first}() { return this.${column.fieldName}; } public void set${column.fieldName?cap_first}(${column.javaType} ${column.fieldName}) { this.${column.fieldName} ${column.fieldName}; } /#list }模板里用得最多的就是#list循环和?cap_first首字母大写。第一眼看上去模板的语法很啰嗦但模板本身就是要承担展开重复的职责——你现在嫌它麻烦等到要批量改三十个实体的时候就知道这么写有多值了。3.2 写第一版生成主流程生成主流程的核心代码就几十行public class CodeGenerator { private static final String TEMPLATE_DIR /templates; private static final String OUTPUT_DIR output/; public static void main(String[] args) throws Exception { // 1. 读取元数据 ObjectMapper mapper new ObjectMapper(); MetadataModel model mapper.readValue( new File(generator-config/metadata.json), MetadataModel.class); // 2. 初始化 FreeMarker Configuration cfg new Configuration(Configuration.VERSION_2_3_32); cfg.setClassLoaderForTemplateLoading( CodeGenerator.class.getClassLoader(), TEMPLATE_DIR); cfg.setDefaultEncoding(UTF-8); cfg.setOutputEncoding(UTF-8); cfg.setWhitespaceStripping(true); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // 3. 逐表执行生成 for (TableMeta meta : model.getTables()) { generateEntity(cfg, meta); } System.out.println(生成完成输出目录: OUTPUT_DIR); } private static void generateEntity(Configuration cfg, TableMeta meta) throws Exception { Template template cfg.getTemplate(entity.ftl); Path outputPath Paths.get(OUTPUT_DIR, String.join(/, meta.getPackageName().split(\\.)), entity, meta.getClassName() Entity.java); Files.createDirectories(outputPath.getParent()); try (Writer writer Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)) { template.process(meta, writer); } } }这段代码里有几个细节值得说明。一是setWhitespaceStripping(true)不配这个模板里指令行留下的换行符会变成输出文件里的空行Java 文件不至于编译失败但很难看。二是Files.newBufferedWriter显式指定UTF_8不指定的话在 Windows 上容易踩编码坑这个到踩坑章节细说。三是输出路径用包名拆目录保证生成出来的文件结构可以直接拷进目标工程。跑完这个 main你会在output/目录下拿到一个可以编译的实体类。到这一步一个最小可用生成器已经诞生了——它的能力边界是生成一个实体类但骨架已经完全立起来了。3.3 一个能用的生成器至少要有三种运行模式第一版我只有上面的全量生成用了一个月就觉得不够补了两种模式强烈建议你一开始就设计进去。全量生成full上面这段 main 的默认行为遍历所有表、生成所有文件、覆盖输出目录。增量生成incremental增加一个--only-new参数目标文件已存在就跳过。适合给已上线的工程补新模块不会干扰已有代码。演练模式dry-run增加一个--dry-run参数只打印将要生成哪些文件、写到哪个路径不真正落盘。这个模式在调模板时极其好用我后面专门讲模板调试时还会提到。三种模式用一个简单的参数解析就能区分成本很低收益却是立竿见影的。没有 dry-run 的时候我每次改模板都要跑到 output 目录里看文件看完还要手动清理多一步操作效率就低一截。4. 模板设计方法论写模板比写业务代码更考验设计生成器跑起来之后你就进入了一个更磨人的阶段写模板。我曾经天真地以为模板就是把业务代码抽几个变量出来实际写了十几个模板之后才发现模板设计的难度不亚于设计一个 API——因为它要在通用和可读之间找平衡。4.1 模板分层宏、片段、引用复制粘贴时代每个文件的文件头注释、import 块、类的公共注解都是重复的。到了模板世界这些重复不会自己消失只是从代码里复制变成模板里复制。如果每个模板各写各的后期统一改一个公共注解就得改十几个模板文件那和手工改代码有什么区别所以模板也要分层。把公共片段抽出来放在一个common.ftl里#macro fileHeader comment /** * ${comment} * 本文件由代码生成器生成请勿手动修改如需修改请编辑元数据后重新生成。 */ /#macro然后在具体的模板里引用#include /common.ftl fileHeader comment${meta.comment}/这样公共逻辑只有一份后续要在所有生成文件的头部加个生成时间或者生成器版本号只需要改宏定义一处。我把这个实践叫作模板的单一事实来源和代码里 DRY 原则是一个道理。一个几十个模板的生成器项目没有这一层抽象维护成本会失控。4.2 把复杂逻辑留在模型里不要让模板背着模板引擎虽然支持条件判断、循环、甚至自定义函数但模板里塞的业务逻辑越复杂排障越痛苦。FreeMarker 的报错信息比很多引擎好但和 Java 的堆栈比起来还是不够直观而且模板很难写单元测试。我的原则是模板只做三种事——取值、循环、简单判断。剩下的逻辑全部下沉到元数据模型的 Java 方法里。最典型的例子是 JDBC 类型到 Java 类型的映射。你当然可以在模板里写一个巨大的#switch但更好的做法是在ColumnMeta里加一个方法public class ColumnMeta { // 其他字段省略 public String getJavaType() { return switch (this.jdbcType) { case VARCHAR, CHAR, TEXT - String; case BIGINT - Long; case INT, INTEGER - Integer; case DATETIME, TIMESTAMP - LocalDateTime; case DECIMAL, NUMERIC - BigDecimal; default - String; }; } }模板那边只需要写${column.javaType}拿到一个已经算好的值。类型映射这种逻辑放进 Java 方法之后可以被单元测试覆盖可以在多个模板里复用修改时只用改一处——而模板瘦身之后也更容易阅读和维护。还有一个好处是模板的幂等性更强。模型方法逻辑是确定的同样的元数据永远算出同样的值模板不会因为你忘了某个映射分支而悄悄生成一个错误的类型。4.3 让生成的代码可读且可维护生成器产出的代码最后是要进 code review 的。如果生成出来的代码风格怪异、命名晦涩就算功能是对的review 的人也难受改的人也难受。几个我总结出来的实操原则命名和团队手写代码完全一致。模板里类的命名、方法命名、变量命名都要对标团队现有的规范不要因为是从模板里出来的就随意。比如我们团队实体命名是XxxEntity那模板就把这个后缀写死哪怕元数据里没有存这个信息。在文件头写清楚我是谁。注释里说明这是生成文件、由什么生成的避免后来的人以为这是手写代码直接在文件里改了之后被下次生成覆盖。结合作业区标记比如如需修改请先改元数据等于提前给后人排雷。注释质量不低于手写代码。我见过一些生成器的模板实体字段全没有注释结果生成出来的代码除了字段名什么也看不懂。元数据里明明有 comment模板里却没取——这属于白白浪费信息。字段注释、方法注释、类注释都是元数据的一部分能带上的都带上。保持结构简单。生成器不要试图生成复杂的继承链、泛型嵌套、设计模式。它擅长的是把重复结构批量产出一旦模板里出现大量条件分支嵌套说明你要么应该把逻辑下沉到模型里要么这个场景根本不适合生成。5. 踩坑实录那些让我翻过车的真实问题写生成器这一年半里翻车的次数不算少。挑四个最有代表性的问题讲透每一个都是我真实踩过、也真实排查出来的。5.1 空白行与缩进FreeMarker 的换行陷阱第一次把生成出来的实体类打开我愣住了每个 getter/setter 之间空了三行类的开头还有个多余的空行。代码能编译但丑得没法交差。原因其实很简单FreeMarker 的指令标签#list、#if、#assign这类默认会自己独占一行指令本身不输出内容但它后面紧跟的那个换行符会被原样输出到文件里。所以指令用得越多生成的代码空行越多。解决办法有几个层面。首先打开cfg.setWhitespaceStripping(true)这个配置能去掉模板里那些只有空白和指令的行的输出解决大部分问题。其次模板写的时候把指令贴紧内容行比如#if column.primaryKeyId GeneratedValue(strategy GenerationType.IDENTITY) /#if这样条件判断就不会在多行之间留下空行。最后一个兜底方案生成完之后用统一的格式化工具我们用的 google-java-format / Spotless把文件过一遍缩进和空行全部交给工具处理。我的最终实践是模板负责内容正确、格式化工具负责姿势好看两者配合生成的代码看起来和手写的一模一样。5.2 编码陷阱中文注释为什么总是乱码这个坑我印象太深了。有一版生成器在同事的 Windows 机器上跑生成的实体类所有中文注释全部变成乱码而在我的 Mac 上跑又是正常的。查了大半天根因是 FreeMarker 的默认编码跟随系统平台Windows 上默认 GBK而模板文件是 UTF-8 存的两边对不上中文自然就花了。解决方式分三层缺一不可模板文件本身统一存成 UTF-8无 BOMIDE 里设置好别用记事本改模板。FreeMarker 环境里显式配置cfg.setDefaultEncoding(UTF-8)和cfg.setOutputEncoding(UTF-8)两个都要写。很多教程只写第一个但setDefaultEncoding只管模板的读取输出的Writer编码还得靠第二个。写文件时用Files.newBufferedWriter(path, StandardCharsets.UTF_8)明确指定字符集。另外提醒一个隐蔽细节Windows 下用 Notepad 保存文件会偷偷加 BOM 头模板一旦带了 BOM生成的代码第一行会出现一个不可见的乱码字符编译的时候报一个莫名其妙的错。排查方法是把模板文件用十六进制编辑器看一眼开头有没有EF BB BF有就去掉。5.3 用户代码被覆盖的惨案前面提到我用覆盖策略结果在一个项目上出了事故。当时有个同事对生成的OrderServiceImpl做了大量手工修改增加了一整套订单状态流转的逻辑。后来表结构加了一个字段他重新跑了一遍生成器——所有手工修改全部被覆盖git 里连历史都差点找不到。那次之后我立了几条规矩第一条生成文件必须带文件头标记。模板统一生成一行注释本文件由代码生成器生成请勿手动修改凡是看到这行注释就不建议直接在文件里手工改。这样做的潜台词是你需要一个机制来区分这个文件只属于生成器和这个文件生成完了归我管。第二条可能存在手工代码的文件改为跳过或备份策略。不是所有文件都要每次全量覆盖。ServiceImpl 这种大概率会被业务逻辑填满的文件我直接改成--only-new模式下跳过或者生成前自动把旧文件备份成带时间戳的副本。第三条生成完先看 git diff 再提交。这一步是和覆盖策略配套的保险丝。生成器本身应该稳定元数据没变、模板没变的情况下重复生成的结果应该一模一样如果你发现 diff 里出现了不该出现的改动就要停下来查原因而不是稀里糊涂先提交。5.4 模板调试的土办法模板报错的信息往往比较抽象尤其是数据类型不匹配、空值访问这类问题。我自己摸索出几个土但很管用的调试手段。最常用的是先渲染到内存再决定要不要落盘。写一个调试入口把模板渲染成一个字符串打印出来而不是直接写文件。配合--dry-run模式我可以在不产生任何文件的情况下反复调整模板、立刻看到输出效果。第二个手段是隔离问题文件。生成器一次生成四五个文件某个文件出问题时先写一个只渲染单个模板的测试方法把元数据固定成一个最小样例快速定位是这个模板的问题还是元数据的问题。最小样例特别重要一个表、两个字段比在几十个表的真实元数据里排查快得多。第三个手段是看 FreeMarker 报错里的模板行号。FreeMarker 的异常信息会指到.ftl文件的具体行我先去那行检查——百分之七八十的问题都是null值访问比如某个元数据对象没有某个字段模板里却取了。这种情况我会在元数据模型里给字段加默认值从源头上防住。另外Linux 下有cat -A可以查看文件里不可见的换行符和缩进排查空行问题时屡试不爽。这个命令很多人不知道但真的是排查模板输出问题的利器。6. 进阶方向从一个模块到一套脚手架最小版本跑通、模板规范也建立起来之后生成器的价值就从省复制粘贴的力升级成了快速搭模块脚手架。这节讲三个我实际推进过的方向每一个都能明显提升效率但也各有要注意的边界。6.1 反向读取数据库结构让元数据自动生成手写元数据在小规模时很舒服但表一多就变成新的体力活五六十张表每张表十几个字段手写 JSON 能写到怀疑人生。这时候就该让程序从数据库里把元数据读出来。我用的是 JDBC 直连查询information_schema.COLUMNS视图SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA ? AND TABLE_NAME ? ORDER BY ORDINAL_POSITION;拿到列信息之后在 Java 代码里完成两件事下划线列名转驼峰属性名JDBC 类型映射成 Java 类型——这两套逻辑前面都已经在模型里躺着了直接复用。表注释、列注释都从数据库里取连手填的功夫都省了。这一步之后生成器的流程就变成连上测试库解析出所有表的元数据对元数据做微调比如指定哪些表不生成、哪些字段不参与生成一键生成全部模块。我在这条路上提醒一个注意点数据库是动态的表结构变了元数据也要跟着变。所以从数据库读元数据这个功能应当每次生成前都重新读一遍而不是读一次存成文件以后就再也不更新——否则你会发现元数据悄悄和数据库脱节了。6.2 生成后的格式化与编译校验生成器最容易犯的毛病是只求能编译不求质量。这句话反过来讲如果连编译都没跑过怎么敢说生成结果是可用的我的实践是给生成器接两个阀门。第一个是格式化阀门生成完文件之后调用 Spotless 或 google-java-format 把 Java 文件统一格式化一遍。这样模板即使写得稀烂输出也能保持统一风格模板维护的压力会小很多。第二个是编译阀门在 CI 里加一个专门的 job把生成的代码拷贝到一个干净的测试工程里执行mvn compile编译失败就直接让构建挂掉。更进一步我还在测试里加了一个黄金文件校验用固定的元数据样例行生成一遍把结果存成基准文件每次改动模板之后跑一次对比凡是和基准结果不一致的地方都要人工确认。这个机制保证了模板不会在无意中被改坏改模板变得像改接口一样有回归保障。6.3 把生成器本身当作产品来迭代最后想聊聊心态。生成器不是写完就扔的脚本它会陪伴你的项目很久所以值得像产品一样维护。我给自己的几条迭代纪律元数据即代码。元数据 JSON 存进 git和代码一样走分支、走 review。哪天库表结构变了、元数据没跟上代码 review 时一眼就能看出来。模板改动要过会。改一个公共模板意味着所有模块的代码风格变化影响面比改个别模块大得多。我的做法是模板改动必须同步更新示例输出让 review 的人直观看到改动效果。先统计再决定要不要加新模板。我的原则很简单同一个类型的文件三个月内手工写过三次以上才值得为它加一个模板。生成器是在收敛团队的重复劳动而不是展开控制面——模板数量太多、太杂本身就会变成一种新的维护负担。说到这再分享最后一个使用习惯我现在接一个新模块第一件事不是新建文件而是去补元数据。这个动作本身就像是在给模块画一张结构图——表结构、字段含义、包名归属全部在 JSON 里确定下来之后再生成代码开发流程好像突然变得顺了。生成器的本质就是把你想清楚的东西用最快、最一致的方式落成代码。只要抓住元数据是源头、模板是手段、一致性是目标这三句话你的生成器就不会跑偏。
返回列表