一个写了三十一期的系列,还能一直有东西可讲,本身就能说明一些问题。这期我想把“CleanCode + AI编程标准代码生成器”这套东西掰开揉碎,讲讲为什么我一直强调“生成即规范”,以及在实际项目里,它到底是怎么帮我从源头把技术债摁死的。
先给没看过前三十期的朋友交代一下背景。我这边接手的项目,很多是体量大、历史包袱重的业务系统,代码里充斥着“能跑就行”的妥协:命名随意、函数上千行、重复逻辑遍地、注释在说废话。传统做法是等代码写完了,再上SonarQube、ESLint这类工具去扫,扫出来的问题再人肉改。这话没错,但方向就反了。你想想,工具能扫出来的,都是已经发生的问题,发现问题再修,那是事后补救,成本最高。而AI编程标准代码生成器这套思路,核心就一句话:让AI只按你的规范写代码,而不是让AI自由发挥再帮你改。
这期是第三十一弹,我不谈虚的,就从设计思路、实际配置、调测技巧到踩坑实录,把整套方案完完整整过一遍。
1. 内容整体设计与思路拆解
1.1 为什么说“生成即规范”是源头治理
如果你用过几款AI编程工具,大概率遇到过这种情况:让AI写一个查询函数,它给你吐出来一堆没用到的import,变量名一会儿a一会儿tempData,异常处理只接住不抛出,甚至把之前的业务逻辑猜了个面目全非。这种代码不是不能跑,是跑了之后你会陷入恐怖的维护地狱。
根源在于,通用AI模型训练数据来自海量开源代码,那里面什么风格都有,良莠不齐。你要是不加约束,AI当然倾向于“平均水准”——整个开源社区的平均水准。而标准代码生成器做的事情,本质上是给AI加了一层“规矩层”。它在AI和大模型之间架起一道过滤网:先把业务需求解析成结构化任务,再按你定义好的规范模板去生成代码。这样一来,产出的代码从一开始就是符合统一风格、有完整错误处理、有日志链路、有基础测试的“成品”,而不是需要二次加工的“半成品”。
打个比方,普通AI编程像是雇了一个很有干劲但不熟悉你公司规章制度的新人,你跟他描述需求,他凭直觉写东西,写完了你再逐行review、指正、让他改。而标准代码生成器,相当于你给这个新人一本厚重的《团队开发规范手册》,并让他必须照着手册执行,他写的每一行代码都要过一遍手册里的检查表。你要做的,就是审一下业务逻辑对不对,而不是跟代码风格做斗争。
这两个模式的质变点在于:技术债的产生成本被前置到了生成阶段,而修复成本被压到了最小。后者的成本可能是前者的十分之一甚至更低。
1.2 这套方案的核心模块划分
我这边落地的时候,把整个系统分成了五个核心模块:
- 规范约束层:这是一切的基石。包含了命名规范、分层规范、异常处理规范、日志规范、注释规范、事务规范、幂等规范等。
- 模板引擎层:把规范转化成可执行的代码模板。不同场景(如REST接口、定时任务、消息消费者、文件批处理)有专门的模板。
- AI路由与上下文组装层:负责把用户的自然语言需求,转化成AI能理解的高质量提示词,并注入相关的项目上下文,比如表结构、已有接口、依赖版本。
- 生成校验层:代码生成出来后,不直接交付。先做静态规则校验、格式检查、依赖检查,必要时跑一下编译与会话级的测试。
- 审计与反馈层:记录每次生成的内容和后续改动,反哺规范库,让规则越用越精准。
这五个模块里,最容易被人忽略的是第一层和最后一层。很多人搞AI编程只盯着“生成”那一瞬间的效果,忽略了规则本身是一个需要持续迭代的生命体。规范库不更新,生成器就是一个固定动作的复读机;规范库有人维护、有人喂数据,它才会越用越聪明。
1.3 CleanCode在其中的角色
CleanCode(整洁代码)在这里不是一句口号,而是具体到字符级别的规则集。我整理过一份关键词对照表,写进了规范库里:
| 维度 | 劣质标识(触发警告) | 优质标识(生成目标) |
|---|---|---|
| 命名 | data1、temp、res、flag2 | customerOrderList、pendingApprovalCount |
| 函数长度 | 超过80行 | 控制在20-30行,职责单一 |
| 嵌套层级 | if/else超过3层 | 卫语句提前返回,或策略模式拆解 |
| 重复度 | 相似代码块重复出现3次以上 | 抽取公共方法或工具类 |
| 注释 | 注释解释“怎么做” | 注释说明“为什么这么做” |
| 错误处理 | 捕获后log.error然后返回null | 捕获后包装上下文并抛出业务异常 |
这些规则不是拍脑袋定的,而是从过去两年多个项目的重构总结里提炼出来的。放到AI生成环节里,它们会被翻译成提示词里的硬性指令和模板里的占位符逻辑。
2. 核心细节解析与实操要点
2.1 提示词工程:让AI懂“规矩”而不是“灵感”
很多人以为AI编程的核心是模型,其实在落地场景里,提示词的质量决定了最终代码的下限。我的做法是,不聊“帮我写一个用户查询接口”,而是给AI一份结构化的任务卡。
这里分享一个我常用的提示词骨架,你可以直接抄去改:
你是一个遵循XXX团队规范的高级Java工程师。请在生成代码时严格执行以下规则: 【背景】 - 项目使用Spring Boot 3.x + MyBatis-Plus - 数据库表:t_customer_order,字段见DDL - 现有分层:Controller -> Service -> Manager -> Mapper 【任务】 为“分页查询客户订单列表”功能生成完整代码。 要求: 1. Controller只做参数接收和响应包装,禁止写业务逻辑。 2. Service层必须包含业务校验、状态流转、异常转换。 3. 所有方法必须有Javadoc,说明业务场景和参数含义。 4. 异常必须使用BizException并携带错误码,禁止抛出裸RuntimeException。 5. 日志必须包含入参标识(traceId),禁止打印敏感字段。 6. 查询必须使用分页插件,禁止全表查询。 7. 函数体不得超过40行,如果超过必须拆分子方法。 【输出格式】 按Controller、Service、ServiceImpl、Mapper、MapperXML五个文件分别输出。 每个文件给出完整内容,不允许省略任何部分。这里有几个点可以讲透。第一,背景信息要具体到“表结构”和“分层方式”,含糊的需求只能得到含糊的代码。第二,规则要可执行、可检查,比如“函数体不得超过40行”,AI就是能按照这个硬约束去拆解,而不是“请写出整洁的代码”这种没有指导意义的话。第三,输出格式要明确,否则AI可能会把代码混在一个段落里,你还得自己拆文件。
2.2 代码模板设计:把规范“焊死”在骨架上
光靠提示词还不够,因为提示词是“短期记忆”,模型生成的代码仍然会有不确定性。更稳的做法是,把规范直接变成代码模板的骨架。
以Service层为例,我的标准模板长这样:
/** * {业务名称} Service * * @author auto-generator * @since {date} */ @Service @RequiredArgsConstructor @Slf4j public class CustomerOrderServiceImpl implements CustomerOrderService { private final CustomerOrderMapper customerOrderMapper; /** * 分页查询{业务对象}列表 * * @param query 查询条件 * @return 分页结果 */ @Override public PageResult<CustomerOrderVO> pageQuery(CustomerOrderQuery query) { // 1. 参数校验(由Validator完成,这里只做兜底) // 2. 组装查询条件 // 3. 执行分页查询 // 4. 转换VO并返回 return null; // TODO: 由AI填充完整逻辑 } }AI要做的,就是把{业务名称}替换成真实业务,并填充各个步骤的内部逻辑。这个模板本身就是规范,AI在它的约束下,想写出“乱来”的代码都难。
设计模板时最值得注意的一点:不需要把每个方法都封死。模板的重点是约束“结构”,而不是约束“实现细节”。留一些合理的空间让AI去发挥,比如具体查询条件怎么拼、VO怎么转换,这些属于业务逻辑,不该被模板锁死。结构规范 + 逻辑自由,是最平衡的组合。
2.3 配置中心:一套规则,多语言复用
我现在的项目是Java为主,但偶尔也要生成Python脚本或Go服务。如果你想让这套体系通用,一定要把规范层和模板层解耦。
我的做法是用YAML维护一套中性规范,再通过各语言的模板引擎渲染:
rules: naming: method: camelCase constant: UPPER_SNAKE function: maxLines: 40 maxNestingDepth: 3 error: useBusinessException: true includeErrorCode: true logging: includeTraceId: true这套中性规范被加载后,Java模板引擎和Python模板引擎各自消费,生成不同语言的代码时遵守同一套底层规约。这样你带多个技术栈项目时,维护成本不会线性上涨,而是几乎持平。
3. 实操过程与核心环节实现
3.1 从零搭建一套生成器的最小闭环
这章节我会一步一步带你搭一个最小可用的生成闭环。不需要你有多深的AI基础,跟着做就行。
第一步:定义你的规范文件
在项目根目录创建.ai-rules/standard.yaml,内容参考上面的规则示例。这个规范文件是你体系的宪法,一定要结合你们团队自己的奖惩机制来定。什么叫结合团队实际情况?比如你们团队历史上因为“空指针”出了好几次线上事故,那null处理就要单独立一条强制规则;如果经常因为“分页失效导致内存爆掉”,那分页规则也要重点写。
第二步:编写任务解析器
这一步是把你输入的描述性的需求,结构化成一个任务对象。我用Python写过一个极简版:
from dataclasses import dataclass import re @dataclass class CodeTask: module_name: str task_type: str # controller/service/mapper/full_stack requirements: str table_name: str = "" def parse_task(user_input: str) -> CodeTask: # 简单解析:提取表名和任务类型 table_match = re.search(r"表[::]?(\w+)", user_input) module_name = user_input.strip().split()[0] task_type = "full_stack" if "接口" in user_input: task_type = "controller" elif "service" in user_input.lower(): task_type = "service" return CodeTask( module_name=module_name, task_type=task_type, requirements=user_input, table_name=table_match.group(1) if table_match else "" )这一步的实际意义是,把自然语言里的“噪音”剥离出去,让后续提示词组装时,系统知道该往哪个模板里塞东西。生产环境里这一步会复杂很多,要接上需求管理系统的工单数据、数据库元数据、甚至git历史里的关联改动,但思路是一致的。
第三步:组装带规范的提示词
根据CodeTask的类型,先把规范文件读进来转成文本,再拼模板:
import yaml def build_prompt(task: CodeTask, template_name: str) -> str: with open(".ai-rules/standard.yaml", "r", encoding="utf-8") as f: rules = yaml.safe_load(f) rule_text = yaml.dump(rules, allow_unicode=True) prompt = f"""你是严格遵守规范的代码生成器。 请根据以下规则生成代码: {rule_text} 任务类型:{task.task_type} 模块名称:{task.module_name} 具体需求:{task.requirements} 表信息:{task.table_name} 请确保所有生成代码符合上述规范,并在代码输出前先输出一段说明,列出你为此需求做的技术决策。 """ return prompt注意我要求AI“先输出决策说明,再输出代码”。这一步非常关键。为什么?因为它强迫AI在动手之前想清楚技术方案,而不是上来就堆代码。我实测下来,加了这一步之后,生成的代码逻辑混乱率大幅下降。
第四步:生成与自动校验
调用你的AI编程工具的API(Codex、通义灵码、CodeGeeX等都支持API方式),得到结果后,不要急着复制粘贴。先做三层校验:
- 格式校验:检查代码能否通过编译或语法检查。
- 规则校验:用正则或AST解析去检查关键规范是否遵守,比如有没有出现
System.out.println、函数是否超过行数上限、有没有未使用的import。 - 接口契约校验:如果生成的是API层代码,检查接口路径、请求方法、参数注解是否和设计文档一致。
我通常是写一个CI脚本,把这三层校验接入GitHub Actions。每次AI生成代码,自动跑校验,失败就打回重新生成。
3.2 参数计算与模型选择:不同场景不同策略
AI编程工具的选择,也是一门学问。我自己的经验是分三档:
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 日常CRUD接口生成 | Codex / 通义灵码 | 上下文理解好,代码补全自然 |
| 复杂业务逻辑生成 | Claude(Opus级模型) | 长上下文推理强,能处理跨文件依赖 |
| 大规模重构/批量代码迁移 | 本地微调模型(如StarCoder) | 数据可控,规则一致性强 |
这里特别提醒一个坑:不要迷信单个模型能打通所有场景。做生成器的时候,最好抽象出一层“模型网关”,根据任务难度路由到不同模型。比如简单查询用便宜快速的模型,复杂业务用贵但理解力强的模型。我这边实测下来,这个策略能把单位生成成本降低40%以上,同时质量不降。
还有一个重要参数是温度(temperature)。代码生成场景,温度建议设在0.2以下,甚至直接用0。代码不是创意写作,不需要随机性和多样性。温度高了,AI会“灵机一动”给你换个API的写法,哪怕这个API压根不存在于项目依赖里。
3.3 从生成到入库:不经过Review的代码不进主干
生成器跑得再顺,也不能让代码直接合入主干。我的流程是:
- AI生成代码 → 2. 自动校验 → 3. 生成代码提交到Feature分支 → 4. 触发CI全量测试 → 5. 人工Review(只Review业务逻辑,不看格式) → 6. 合入主干。
这第5步很关键,因为它的存在,人工从“逐行检查”变成了“重点抽检”。我用这套流程跑下来,一个模块的从需求到入库时间,缩短了大概一半,而且返工率明显低于纯人工开发。
3.4 实际案例:一个“订单导出”功能的全流程生成记录
拿最近做的一个功能举例。需求是“按条件导出客户订单Excel,超过10万条要分批写”。我把任务丢给生成器,它输出的技术决策是:
- 使用EasyExcel流式导出,避免内存溢出
- 分页查询,每批5000条,写完即释放
- 异步任务 + 任务状态表,前端轮询进度
- 文件名加入时间戳和随机数,防止重复
然后生成的代码里,Controller干净地只接收参数,Service层有校验和状态流转,异步任务的线程池配置走了全局配置,没写死。我Review的时候只需要看两个问题:查询条件拼得对不对、任务状态流转有没有漏洞。这种代码交到手上,心情是舒坦的。
4. 常见问题与排查技巧实录
4.1 AI“装作”遵守规范,实际又乱来
这是最常遇到的问题。AI会在你要求“禁止System.out.println”之后,仍然偷偷输出System.out.println。排查下来,大多数原因是模型的上下文窗口把它早期的“坏习惯”带出来了。
我的应对办法有两个。第一,在提示词最末尾加一句“生成完毕后,请自查一遍代码,确保没有违反上述任何一条规则。”让模型强制做一次反思。第二,靠自动化校验兜底,用正则直接扫System.out,存在就自动打回。
这里提个不太为人知的技巧:你可以把校验失败的错误信息作为反馈,再喂给AI重新生成。比如“你的代码第35行违反了函数不超过40行的规则”,AI看到这个错误后,第二次生成的合规率会明显提高。这算是给AI加了“反思机制”。
4.2 生成代码引用了不存在的依赖或API
模型幻觉是AI编程的固有缺陷。它会因为训练数据里见过AWS SDK的某个方法名,就在你的项目里也“想当然”地用上,哪怕你的pom.xml里压根没有这个依赖。
我的解决思路是:在上下文组装时,把依赖清单注入进去。就是把项目里现有的依赖版本列表提取出来,放进提示词里作为约束。例如:“本项目的Spring Boot版本为3.2.4,请勿引入未在以下列表中的新依赖:Spring Web, MyBatis-Plus, Hutool, EasyExcel, ...”。注入依赖列表的作用是让AI在可控范围内创作,而不是凭空造轮子。
4.3 日志链路丢失:查问题查到头秃
AI生成的代码如果忽略了日志,线上出了问题你会特别痛苦。我要求所有Service层入口和出口都有日志,且必须带上traceId。实际做的时候,光靠提示词不够,还是要在模板里固定下来:
@Slf4j public class CustomerOrderServiceImpl implements CustomerOrderService { @Override public PageResult<CustomerOrderVO> pageQuery(CustomerOrderQuery query) { String traceId = TraceIdUtil.get(); log.info("[traceId={}] 分页查询客户订单开始, query={}", traceId, query); try { // 业务逻辑 log.info("[traceId={}] 分页查询客户订单结束, total={}", traceId, total); return result; } catch (Exception e) { log.error("[traceId={}] 分页查询客户订单异常", traceId, e); throw new BizException(ErrorCode.ORDER_QUERY_FAILED, e); } } }这样生成的代码无需额外写日志,天然具备可观测性。
4.4 不同AI工具生成的代码风格漂移
如果你用的是外部工具的Web界面,而不是API可控体系,很可能会出现周三用工具A生成的代码和周四用工具B生成的代码风格完全不一致。这在团队协作里会造成极大的维护负担。
所以我在团队里强制要求:AI生成只能走统一入口,不允许开发者在个人工具里“自由发挥”然后把代码贴到主干。如果你一定要用个人工具,那生成后至少要经过自动化格式化(如Java的Spotless + Google Java Format),把风格拉齐,再加规范校验。风格问题看起来小,堆多了就是巨头痛。
4.5 一个容易被忽略的“技术债新形式”:生成代码的过度复杂
AI有时候会把简单问题复杂化。你让它写一个简单的findById,它给你搞出泛型工厂、策略模式、装饰器链,美其名曰“为将来扩展做准备”。这种代码就是技术债新形态——不是烂得不能看,而是过度设计导致理解成本飙升。
我发现要治这个问题,必须在规范里明确写一条:“只解决当前需求,禁止YAGNI(You Aren't Gonna Need It)式扩展”。让AI明白,你现在要的是一个简单的查询方法,不是框架演进蓝图。规则越具体,AI就越“安分”。
5. 维护与技术债回收:生成器的长期价值
5.1 规范库必须是活文档
我已经强调多次了,最后再展开讲讲。这套生成器用久了,你会慢慢发现,规则库比代码本身更值钱。因为代码会换,框架会升级,业务会变,但一套好的规则库,是团队心血的沉淀。
维护方法很简单:每次事后Review发现代码问题,都问一句“这个问题能不能在生成环节就规避掉?”如果可以,就把规则加进去。比如我就因为一次线上“导出超时”事故,给生成器加了规则:“所有批量导出功能,必须走异步任务,禁止同步响应。”从那以后,再也没有人能用AI生成出同步导出的接口。
5.2 用生成器反向清理存量代码
第三十一弹的内容也可以反向用。存量代码杂而乱,你可以拿AI标准代码生成器,把老功能重新生成一遍。相当于用新代码把旧代码“重写”一遍,遵守统一规范、有日志、有异常处理、有测试。再通过对比测试(比如接口入参出参对比回归)来保证行为一致。
我用这个方法干过一个旧的报表模块,原来那批代码每个方法都有800行,状态全靠全局变量,重构代价巨大。用生成器重新生成基准代码后,业务逻辑再通过人工把特殊case一点点补回来,整体难度比逐行读旧代码再改低不少。
5.3 团队推广的三阶段路径
最后分享一下我在团队里推行这套方案时踩过的坑和经验,分三个阶段:
- 第一阶段:工具引入期。先不强制,找2-3个愿意尝试的开发,在非核心模块试水。收集数据:生成效率、返工率、bug率。用数据和效果说话。
- 第二阶段:规范建设期。让试水团队把发现的规范问题沉淀成规则,逐步补充到规范库。这个阶段的关键是“高频迭代”,每周至少更新一次规则清单。
- 第三阶段:全面推广期。规范库稳定后,再全团队强制接入。配合生成校验流水线,不经过生成器校验的代码,合入MR会得到机器人提醒。
这里最核心的经验教训就是:不要在第二阶段之前强行第三阶段。规则还不成熟就大规模铺开,团队会被各种生成问题搞得怨声载道,然后这项目就被批成“花架子”,后面再想推就难了。
我个人在实际使用中体会最深的一点是,这套东西最厉害的地方不是让你“少写代码”,而是它逼着你去想清楚“什么样的代码才算好代码”。AI只是放大器,你清楚地知道什么是好,它才能照着你的标准去生产;你不知道什么是好,它只会加快你产出垃圾的速度。所以,别急着去找最强模型,先花点时间,把你们团队的“标准”定义清楚。标准立住了,工具才能产生真正的复利。