
作为一个常年和AI编程工具打交道的人说实话工具确实让写代码快了不少但最近一年我越来越明显地感觉到一件事AI生成的代码正在悄悄给团队制造一种新的“技术债”。这种债不是跑不通的bug而是更隐蔽的——风格碎片化、边界处理缺位、注释废话连篇、异常逻辑随手一抛。代码能跑但没人敢碰改一行要心惊胆战半天。所以当我开始做“CleanCode AI编程标准代码生成器”这个系列的时候想解决的就是这个最根本的问题能不能让AI在生成的源头就输出符合团队规范的代码而不是等代码写完了、合并了、上线了才在Code Review里追着人改。我的答案是把“规范”内置到生成链路里让生成即规范从源头杜绝技术债而且保证易调测、易维护。这一弹我就把整套方案的设计思路、核心实现和落地实操完整地拆开讲清楚第三十五弹聊点真正能落地的东西。1. 先想清楚AI生成代码的技术债到底从哪里来1.1 问题不在AI在“没有标准的生成链路”很多团队刚开始用AI编程时都很兴奋觉得效率翻倍但用上两三个月就沉默了。UI能跑、接口能通、单测能过可是当你真的去读这些代码、去改这些代码的时候心里的火气就上来了。我总结了一下AI无约束生成代码普遍存在四个问题。第一风格碎片化。同一个项目里有人习惯返回Result包装对象有人直接返回裸实体有人用构造器创建对象有人用链式setter有人Controller里做参数校验有人Service里才想起来校验。AI每次生成都是“概率采样”下一次生成的结果可能跟上一段代码完全不是一个风格。代码风格不统一看起来像四五个人没有沟通就各写各的。第二结构性缺漏。AI很擅长把主流程写完整但不擅长处理边界分支——事务里漏了回滚条件缓存更新没考虑一致性批量接口没做分页保护。这些结构性的东西如果不在生成阶段就卡死光靠事后review人眼很难全看出来。第三异常逻辑散漫。生成代码里最常见的通病是catch (Exception e) { log.error(e.getMessage()); }直接吞掉异常或者干脆不catch让异常一路抛到前端。真正规范的异常处理应该是分层、分类、有兜底策略的。这需要一套固定的写法约束。第四注释与文档质量低。AI生成的注释绝大多数是在重复代码本身——“把用户的姓名设置为姓名”这种注释一点信息量都没有。更糟的是AI还会编造不存在的业务逻辑然后写进注释里误导后来维护的人。这四类问题本质上都是同一个根源AI生成代码的时候预测目标里只有“像代码”没有“合规范”。模型根据海量语料做概率采样什么风格的代码都有可能出现你让它“自由发挥”它就真的自由发挥。1.2 为什么坚持“源头治理”而不是“事后修”关于怎么解决行业里其实有三种路线。第一种是事后强制。代码生成完了交给lint工具和Code Review去挑刺发现违规再让人工修。这种方案最省事但问题在于——修复成本在后边是呈指数上升的。生成阶段改一个命名可能只要一分钟但合入主干后再改一个公共接口的命名要牵连调用方、测试用例和文档半天都未必够。第二种是生成后自动格式化/自动修复。比如eslint --fix、gofmt这类工具能不能兜底可以解决一部分空白、引号、缩进问题但解决不了结构性问题。gofmt可以把代码排整齐但排不齐“事务是否包裹了完整业务操作”这种逻辑层面的问题。第三种就是我这个系列一直在贯彻的“源头治理”在生成链路里内置规范约束让AI从第一行代码开始就按照固定模板输出。就像装修房子不是等墙砌歪了再请人拆掉重砌而是在施工之前就把结构施工图定好每一块砖该放哪里都有据可查。这是我这套方案价值观最核心的部分——技术债要靠“设计”来消除不能靠“事后打补丁”来缓解。2. 方案的整体设计与核心架构2.1 三层架构不是让AI自由发挥而是给AI一套约束框架整个生成器采用三层架构规范配置层、代码生成层、结果校验层。规范配置层负责把“我们团队的规范”变成机器可读的规则集。这一层是纯配置化的团队里每个人都可以阅读、评审、修改。代码生成层负责把需求描述转化为具体代码但它不是让模型裸奔而是给它喂一套“带约束的提示词模板骨架”。结果校验层负责对生成结果做最终检查用AST解析、规则匹配等手段确认输出真的符合规范而不是停留在“看起来符合”。这一层架构我从一开始就想得很明白不是搞一个“更聪明的AI”来替代现有模型而是把模型包在一个规范框架里让它的能力被引导到正确方向。底层模型可以用主流的大模型API或本地模型但上层必须是一个确定性的工程系统。2.2 为什么规范必须可配置早期我也走过弯路。最开始我尝试把所有规范硬编码在生成器的代码里——Java文件必须带author、方法必须写Javadoc、Controller必须返回Result。但是很快发现一个问题不同团队、不同项目、不同技术栈规范差异非常大。我遇到过一个场景给A团队做的规范里持久层要求用MyBatis-Plus的LambdaQueryWrapperB团队用的是Spring Data JPA这个规范对B团队就是无效甚至有害的。如果规范是硬编码的每接一个团队都要改一遍生成器源码那这个工具就没有推广价值了。所以我最终选择了YAML DSL做规范配置。把“命名规则”“注释要求”“异常处理策略”“事务边界规则”等都定义成配置文件。一个团队接进来只需要提供一份自己的规范描述生成器就能按这套规范输出代码。2.3 模板 规则 模型的协作机制这套方案里生成逻辑是“三层约束”的关系。第一层是模板骨架它定义了代码的结构骨架。比如一个Service接口的实现类骨架就是类声明、字段注入、公共方法、私有方法这个顺序。模板把结构锁死模型就没有机会把结构写乱。第二层是填充规则它定义了模板里“变量部分”怎么写。方法名怎么取、参数怎么校验、异常怎么抛、返回值怎么包装。规则把自由度降到可接受的范围模型只能在这个范围内发挥。第三层才是模型的语言表达能力。它负责把自然语言需求转化为模板里的具体业务逻辑。比如“查用户订单列表”转换为listOrdersByUserId(Long userId, PageParam page)的实现细节。优先级是模板 规则 模型。当模型采样的结果跟模板冲突时模板赢跟规则冲突时规则赢。这个先决条件非常重要如果反过来那这套方案就退化成普通的“AI生成后人工修”了。3. 核心实现规范引擎与代码增强细节3.1 规范引擎怎么做好“生成前、生成中、生成后”三道约束先说生成前约束。这是把规范“翻译”成提示词结构的过程。把“团队规范”中的硬性要求比如必须包含的import列表、必须继承的基类、必须实现的接口直接拼进System Prompt。这能让模型从第一轮生成时就看到规范。然后是生成中约束。这一层我实现了一个模板解析器代码是Jinja2风格的模板加占位符。模型输出的时候我要求它按模板逐段填充而不是一次性生成整个文件。这样占位符之间的依赖关系就是可控的生成错误也能准确定位到某个片段。最核心的是生成后校验。我实现了一个轻量级的AST解析器而不是用正则表达式去匹配文本。为什么要用AST因为AST才是代码真正的语义结构。正则只能看到“有没有return”AST能看到“return是不是在try块内”“有没有在finally里做了return”“事务注解是不是标在了private方法上”。校验规则每条都对应AST上的一种节点模式。3.2 规范配置的YAML示例我直接给一段我实际项目里用的规范配置片段大家可以感受一下配置化的方式。# clean-code-rules.yaml project: name: order-service basePackage: com.example.order language: java style: classDocRequired: true authorTagRequired: false lineLengthLimit: 120 methodNameCamelCase: true structure: controller: extends: BaseController resultWrapper: Result validationAnnotation: true forbidSystemOut: true serviceImpl: interfaceSuffix: Service implSuffix: ServiceImpl transactionAnnotation: true forbidSystemOut: true methodLengthLimit: 60 mapper: interfaceSuffix: Mapper extendsBaseMapper: true exception: strategy: layer-based controller: throw BizException(ErrorCode.PARAM_ERROR) service: throw BizException(ErrorCode.BIZ_ERROR) mapper: translateToDataException comment: classLevel: summary methodLevel: required assertCommentMustExplain: true forbiddenComments: - TODO fix - 此处代码很烂这一段配置里structure和exception是最关键的。structure决定了三层代码的骨架约束exception决定了异常怎么分层处理。有人可能会问forbidSystemOut这种小事有必要写进规范吗太有必要了AI特别喜欢在排查问题时顺手写个System.out.println如果没有这一条约束生成出来100个类里能藏30个控制台输出。3.3 校验规则的落地——把“规范意识”变成代码判断校验层最花时间的不是写校验规则而是定义“什么是违规”。我举一个真实的例子。团队规范要求Service实现类的public方法必须开启事务。这个规则落到AST上需要检查三件事方法是不是public、方法所在类是不是Service实现类、方法上是否标注了Transactional。这三件事在AST里分别对应不同的节点层级只要从类节点往下找到方法节点再检查方法节点的注解列表即可。还有一条更隐蔽的规则Controller方法不允许直接返回实体类。AST校验要检查返回类型是不是com.example.order.entity包下的类。如果返回了实体类直接给出一条修改建议——改为返回VO对象。整个校验器跑完一次会输出一份“违规报告”而且这份报告是结构化JSON可以直接集成到CI流水线里当作代码门禁的一部分。4. 实操全流程从配置到落地的完整复现4.1 接入与初始化我用一个真实的操作场景来说假设我要在“订单服务”里生成一个“分页查询用户订单”的功能模块。第一步是初始化规范环境。把生成器下载到本地执行初始化命令生成clean-code-rules.yaml配置文件。然后根据团队规范修改配置。我们团队用的是Java和Spring Boot所以我在配置里把language设为java把resultWrapper设为Result。第二步是配置大模型API。这里我推荐使用支持函数调用Function Calling的模型接口因为生成过程中需要模型按结构化格式返回内容比如返回JSON片段。函数的强约束能力是纯文本输出比不了的。第三步是跑一次自检。执行generator self-check生成器会输出一个“规范环境自检报告”检查配置文件格式、模型连接是否正常、模板文件是否存在。这一步一定要做很多人跳过之后后续排错会非常痛苦。4.2 典型场景订单分页查询接口的完整生成过程配置好之后直接输入需求描述“生成用户订单分页查询接口按创建时间倒序返回订单号、商品名、金额、状态支持按状态过滤。入参userId、pageNum、pageSize、status。”生成器内部执行流程是这样的第一步解析需求抽取关键要素。生成器先把这段自然语言拆出来资源是“用户订单”操作是“分页查询”排序是“创建时间倒序”过滤条件是“状态”返回字段是“订单号、商品名、金额、状态”。第二步匹配模板。根据“分页查询”这个关键词从模板库里选中“分页查询模板”这个模板定义了Controller、Service、Mapper三层的代码骨架。第三步模型按模板逐段填充。生成器告诉模型“我现在给你一个Controller方法的模板其中方法名占位符为空请你根据需求补充方法名。”模型返回listUserOrders通过命名规则校验后填入占位符。第四步调用校验器。生成完一个完整模块校验器跑一遍规则发现所有规则都通过了才会把代码输出到磁盘同时附带一份“规范符合性说明”。生成的Controller代码大概是这样的RestController RequestMapping(/api/orders) public class UserOrderController extends BaseController { private final UserOrderService userOrderService; public UserOrderController(UserOrderService userOrderService) { this.userOrderService userOrderService; } GetMapping(/user/{userId}) public ResultPageResultUserOrderVO listUserOrders( PathVariable Long userId, RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Integer status) { PageParam pageParam new PageParam(pageNum, pageSize); return success(userOrderService.listUserOrders(userId, pageParam, status)); } }这段代码的规范点在哪里Controller继承了BaseController、参数校验用注解、返回统一包装Result、构造器注入而不是字段注入。这些全部是模板和规则锁定的模型没有机会自由发挥。Service层的实现事务注解、参数校验、分页逻辑也都由模板锁定。生成的Mapper接口继承了BaseMapper分页用了分页插件排序字段由规则校验为“白名单中的字段”防止SQL注入。整个生成过程我记录下来的总耗时大约是90秒左右——模型生成占大头校验和纠正在秒级。4.3 生成后的调测与维护体验代码生成完不是就完事了。我把生成结果导入现有工程跑了一遍单元测试和集成测试结果一次通过。后面有个小需求变更——要在查询结果里加上“商品图片URL”字段。这个改动通过生成器来做就非常顺了。我只需要在需求描述里追加“增加商品图片URL字段”生成器会识别这是“字段追加变更”自动定位到之前的生成记录只更新VO类、组装逻辑和SQL查询字段而不是重新生成整个模块。这就是“易维护”的体现。生成器不是一次性工具它有“增量更新”能力这是因为每次生成都会在本地保留一个生成快照包含这次的完整模板、配置和生成结果。下次变更时基于快照做局部重生成不会影响没有变更的代码块。这个设计是经历过教训后才想明白的。我早期做生成器时每次都是全量生成覆盖有一次改了个字段名生成器直接把另一个服务里手写的一段复杂定制逻辑也覆盖了直接导致线上故障。从那以后我就坚持做增量更新而且强制要求“生成器只改自己生成过的代码”这让维护风险降到了一个可控水平。5. 常见问题与排查技巧实录5.1 生成结果偏离模板怎么办这是使用中最常遇到的问题。模型根本没按模板填占位符直接输出了一整个类。我排查过一段时间原因有几种。最常见的是提示词里模板和需求的位置顺序不对。模板放在后面、需求放在前面模型容易被需求描述带跑。解决办法是模板必须放在System Prompt中需求放User Prompt中并且用分隔符明确划分区域。其次是模板占位符命名太模糊。原来我用{methodName}这种命名模型经常猜不准后来改成{methodName:分页查询方法名}这种“占位符名语义提示”的双重约束效果提升特别明显。模型知道这个位置该干什么不会乱填。再有一种情况是上下文污染。如果之前生成过其他风格的代码模型可能延续那种风格。解决办法是每次生成会话都重置上下文不要在同一会话里连续让模型生成不同模块。5.2 校验器误报与漏报AST校验器最大的坑是误报——把合法代码判成违规。我遇到过一个非常典型的例子校验“Controller不允许返回实体类”时老代码里有一个Deprecated的旧接口确实返回了实体类但它是历史遗留团队明确不做修改。校验器一跑就报错把CI卡死了。后来我增加了“规则豁免名单”。在配置里维护一个白名单针对精确匹配的方法名或路径可以不执行某一条规则。这不是逃避规范而是实现对存量债务的灰度治理——新代码100%合规老代码列计划逐步整改。另一个坑是漏报多发生在泛型和反射场景。比如BaseMapperT的接口AST里看到的是泛型T不是实际类型。如果规则要校验“Mapper是否继承BaseMapper”泛型擦除会让校验器分析不出实际类型。解决方案是做类型消解利用泛型层级关系把T解析为具体实体类。5.3 生成器与人工协作的边界怎么划我必须实话实说这套生成器不是万能的。经过几十个版本的迭代我明确划了一条边界。适合生成器做的CRUD接口、分页查询、简单的业务校验、基础的工具类、规范的单测骨架、领域模型的代码结构。这些代码有模式可循模板能发挥很大作用。不适合生成器做的核心复杂业务的算法逻辑、涉及多个系统深度交互的编排流程、需要依赖人脑业务判断的规则引擎配置。这些代码模板锁不住规则也无法穷举硬套生成器只会生成一个“看似完整实则没法用”的架子。实际的协作流程是生成器负责“骨架常规”人工负责“核心异常”。也就是先把常规代码全部生成好开发者集中精力写那20%真正需要人脑判断的核心逻辑。这个比例在实践中验证下来是性价比最高的。最后分享两个实战心得我在这套生成器上踩过很多坑最核心的一条体会是规范先于模型。别指望模型懂规范先把规范的边界划出来模型的能力才有发挥的空间。任何“让AI生成完再说”的思路最终都会被技术债反噬。再分享一个小技巧。把团队Code Review里反复出现的高频问题固化成一条一条的校验规则。我们团队曾经因为“事务注解写在private方法上”的问题反复review出问题后来我在规则库加了一条“事务方法不可为private”这个问题就再也没出现过。编码规范的最终形态不应该是一份没人读的文档而应该是一套跑在代码生成链路上的硬性校验工具。这就是我坚持做“CleanCode AI编程标准代码生成器”这个系列最核心的原因——把规范内嵌到生成链路里让AI从一开始就写出对的代码。