1. 问题到底出在哪:从一次代码评审说起
上周组里来了个新同事,干活特别快。一个订单导出接口,从建表到联调,半天搞定,跑起来一点毛病没有。结果代码评审的时候,被组长打回去重写了三遍。理由不是有 bug,而是"这代码一看就不是我们组写的"。
这句话挺微妙的。功能是对的,逻辑是通的,测试也过了,但就是"不像"。后来我把那份代码和组里历史代码放一起对比,问题一下就清楚了:命名风格是 AI 味的,异常处理是 AI 味的,连注释的断句方式都是 AI 味的。它像一篇语法完全正确、但口音不对的外语作文。
这个现象现在特别普遍。AI 编程工具越来越强,Cursor、Windsurf、VS Code Copilot、Trae 这些助手写出来的代码,单看某一段几乎挑不出错,但一旦放进一个有历史、有约定、有脾气的团队代码库里,就格格不入。标题里说的"一跑就通,但完全不像我们组写的",说的就是这件事。
这篇东西我想聊的不是"AI 能不能写代码",那个问题早就没争议了。我想聊的是:为什么 AI 写的代码会"不像",这个"不像"具体体现在哪些地方,以及我们怎么把它掰回团队的样子。内容会围绕Java、SpringBoot这类后端场景展开,因为这是目前 AI 编程落地最密集的领域,也是编码规范冲突最明显的地方。不管你是刚用上 AI 助手的新手,还是已经在团队里推 AI 编程规范的老手,应该都能从里面找到能直接抄作业的东西。
先说结论:AI 写的代码"不像",根子不在 AI 笨,而在于它默认的"审美"和你们团队的"审美"不是一套。它学的是全网代码的平均值,而你们团队要的是自己的那一套。平均值听起来很安全,但平均值恰恰意味着没有个性、没有上下文、没有历史包袱的妥协。
2. 为什么 AI 代码总是"差一口气":四个层面的错位
2.1 训练数据的"平均审美"和团队规范的冲突
AI 编程助手的底层逻辑,是从海量公开代码里学"什么样的代码看起来是对的"。这个"对"是统计意义上的对,是出现频率最高的写法。问题就在这:出现频率最高,不等于你们团队在用。
举个最典型的例子。SpringBoot项目里注入依赖,主流有三种写法:字段注入@Autowired、构造器注入、@RequiredArgsConstructor配合 final 字段。全网代码里字段注入出现得最多,所以 AI 默认给你字段注入。但很多规范严格的团队早就禁用了字段注入,要求统一用构造器注入,理由是便于测试、避免循环依赖、字段不可变。AI 不知道你们组的规定,它只知道"这样写最多人用"。
再比如异常处理。AI 特别喜欢写try-catch然后e.printStackTrace(),或者包一层RuntimeException直接抛。这在公开代码里太常见了,但在一个有成体系异常规范的团队里,这属于严重违规。你们可能有统一的BizException、统一的错误码枚举、统一的全局异常处理器,AI 一个都没用上。
提示:AI 的"平均审美"在个人项目里几乎无害,但在团队项目里是灾难。因为团队代码的价值不在于单段正确,而在于整体一致。
2.2 上下文窗口的局限:它看不到你们的"潜规则"
AI 助手再强,它能看到的主要是你当前打开的文件、你贴给它的代码、以及它检索到的少量相关文件。它看不到的东西太多了:你们组的CODING_STYLE.md、历史 PR 里的评审意见、某个类为什么被废弃、某个工具类为什么不能再用。
这些"潜规则"才是团队代码的灵魂。比如你们组约定所有对外接口的 DTO 必须用XxxReq/XxxResp后缀,内部实体才用XxxDTO;比如你们规定所有时间字段统一用LocalDateTime且必须带时区转换工具;比如你们有个祖传的ResultWrapper,所有 Controller 返回值必须包一层。AI 不知道这些,它只会按最通用的方式给你返回一个裸对象。
我见过最离谱的一次,AI 给一个分页查询接口生成了PageHelper的写法,但那个项目早就统一换成了 MyBatis-Plus 的IPage。代码能跑,但和项目里其他几十个接口完全不是一套东西,维护的人一看就头大。
2.3 命名与结构的"通用化"倾向
AI 起名字有个特点:安全、通用、不出错,但也因此毫无信息量。它喜欢用data、result、list、info、handle、process、doSomething这类词。这些词在语法上没问题,但在一个讲究命名的团队里,它们等于没说。
对比一下就很明显。AI 写的:
public Result handleData(List<Data> dataList) { // ... }你们组可能要求的是:
public OrderExportResp exportOrderList(OrderQueryReq queryReq) { // ... }前者你读完不知道它在干嘛,后者一眼就知道是订单导出。AI 不是不会起好名字,而是它默认选择"最不容易被挑错"的名字。通用词永远不会错,但也永远不精确。
结构上也是。AI 喜欢把逻辑全塞在一个方法里,或者机械地按"一个方法做一件事"拆得特别碎。而真实团队往往有自己的分层习惯:Controller 只做参数校验和转换,Service 编排,Manager 处理外部调用,Mapper 只管 SQL。AI 不知道你们的分层边界在哪,它按自己的理解切。
2.4 注释和文档的"AI 腔"
这个可能是最容易被一眼识破的地方。AI 写的注释有个固定套路:先复述方法名,再解释参数,最后来一句"返回处理结果"。比如:
/** * 处理用户数据 * @param user 用户对象 * @return 处理结果 */ public User processUser(User user) {这种注释的信息量约等于零,因为它只是把代码翻译成了中文。团队里真正有价值的注释是解释"为什么":为什么这里要加锁、为什么这个字段允许为空、为什么不能用某个看起来更简单的写法。AI 默认写"是什么",而团队需要的是"为什么"。
而且 AI 的注释断句、用词都有一种微妙的机械感,读多了就能感觉出来。这不是玄学,是因为它的语言分布和人类工程师的日常表达确实不一样。
3. 把 AI 掰回团队风格:一套可落地的实操方案
3.1 先给 AI 立规矩:把团队规范喂进去
最直接的办法,是把你们团队的规范变成 AI 能读懂的输入。别指望 AI 自己猜,你得告诉它。
我现在的做法是在项目根目录放一个AI_RULES.md,内容不是给人看的规范文档,而是专门给 AI 看的"约束清单"。它比普通规范更具体、更命令式。比如:
# AI 编码约束 ## 依赖注入 - 禁止使用 @Autowired 字段注入 - 统一使用 @RequiredArgsConstructor + private final ## 返回值 - Controller 必须返回 Result<T> - 禁止直接返回实体类 ## 命名 - 请求对象后缀 Req,响应对象后缀 Resp - Service 方法动词开头:query/get/create/update/delete ## 异常 - 业务异常统一抛 BizException - 禁止 e.printStackTrace() - 禁止 catch 后吞掉异常然后在每次让 AI 生成代码前,把这个文件的内容贴进对话,或者用 Cursor 的.cursorrules、Trae 的项目规则功能让它自动加载。实测下来,光是这一步,生成代码的"像度"就能提升一大截。
注意:规则要写得像命令,不要写得像建议。"建议使用构造器注入"和"禁止字段注入"对 AI 的效果完全不同,后者约束力强得多。
3.2 用"示例驱动"代替"规则驱动"
光有规则还不够,因为规则是抽象的,AI 容易理解偏。更狠的一招是给它看例子。你们组写得最规范的那个类,直接贴给 AI,说"照这个风格写"。
这叫 few-shot,效果比纯规则好得多。因为 AI 从例子里能学到规则学不到的东西:缩进习惯、空行位置、注释密度、import 顺序、甚至变量命名的语感。
我的习惯是维护一个reference包,里面放几个"标杆类":一个标准 Controller、一个标准 Service、一个标准 DTO、一个标准异常处理。每次让 AI 写新代码,先让它读这几个类。这样它生成的代码,风格会明显向标杆靠拢。
3.3 分阶段生成,别让它一口气写完
AI 一次性生成一大段代码,出错和跑偏的概率最高。更好的做法是拆开:先让它生成接口定义和方法签名,你确认命名和结构没问题,再让它填实现。
比如写一个订单导出功能,我会分三步:
- 先让 AI 生成 Controller 的方法签名和 DTO 定义,我检查命名、返回值、参数是否符合规范。
- 确认后,让它生成 Service 接口和实现骨架,只留空方法体。
- 最后逐个方法填实现,每填一个我扫一眼。
这样每一步的偏差都能及时纠正,不会等到最后发现整段代码都要重写。而且分阶段之后,AI 的上下文更聚焦,生成质量也更高。
3.4 建立"AI 代码评审"环节
AI 写完的代码,不能直接进主干。我们组现在的流程是:AI 生成 → 人工初审 → 跑规范检查 → 再评审。
规范检查这块,静态分析工具能挡掉一大半机械问题。Java生态里,Checkstyle、SpotBugs、Alibaba Java Coding Guidelines 这些插件,能自动查出命名、异常、并发、集合使用等常见问题。把它们的规则配置成和团队规范一致,AI 生成的代码先过一遍,能省掉大量人工返工。
但工具查不出"像不像"这种主观问题。所以人工初审的重点,是看那些工具管不了的地方:命名是否有业务含义、注释是否解释了为什么、分层是否合理、有没有用上团队已有的工具类。
4. 几个高频翻车场景和排查技巧
4.1 场景一:AI 用了过时的 API 或框架版本
这个特别常见。AI 的训练数据有时间滞后,它可能给你生成SpringBoot2.x 的写法,但你们项目已经升到 3.x,javax.*全换成了jakarta.*。代码一编译就报错,或者更坑的是能编译但行为不对。
排查思路很简单:看 import。如果 AI 生成的代码里出现javax.servlet、javax.persistence,而你们项目是 SpringBoot 3.x,那基本可以确定它用的是老版本写法。解决办法是在规则文件里明确写清楚框架版本,比如"本项目使用 SpringBoot 3.2,所有 javax 包已迁移至 jakarta"。
4.2 场景二:AI 生成的 SQL 和项目 ORM 不匹配
SpringBoot项目里 ORM 选型五花八门:MyBatis、MyBatis-Plus、JPA、JOOQ 都有。AI 默认可能给你写原生 JDBC 或者 JPA 的写法,但你们用的是 MyBatis-Plus。结果就是代码能跑,但和项目里其他数据访问层完全不是一套。
我踩过的坑是 AI 给了一个@Query注解的 JPA 写法,但我们项目根本没用 JPA。后来在规则里写死"数据访问统一使用 MyBatis-Plus,禁止引入 JPA 注解",这类问题就少了。
4.3 场景三:AI 忽略了项目的统一返回和异常体系
前面提过,这个是最影响"像不像"的。AI 生成的 Controller 直接返回实体,或者抛裸异常,而你们项目有统一的Result包装和全局异常处理。这种代码功能上没问题,但一看就是"外来户"。
排查方法:搜一下项目里其他 Controller 怎么返回的,对比一下就知道。解决方法是把统一的返回类和异常类作为标杆示例喂给 AI。
4.4 常见问题速查表
| 问题现象 | 根本原因 | 解决方向 |
|---|---|---|
| 命名通用无信息量 | AI 倾向安全词 | 规则里规定命名后缀和动词前缀 |
| 依赖注入方式不对 | 训练数据以字段注入为主 | 规则明确禁止字段注入 |
| 异常处理不规范 | 不知道团队异常体系 | 提供统一异常类作为示例 |
| 用了过时 API | 训练数据滞后 | 规则写明框架版本 |
| 注释只解释是什么 | AI 默认行为 | 规则要求注释解释为什么 |
| 分层边界混乱 | 不知道团队分层约定 | 提供标杆类,分阶段生成 |
| 返回值不统一 | 不知道统一返回类 | 把 Result 类作为必读示例 |
提示:这张表可以贴在团队 wiki 里,新人用 AI 写代码前先扫一眼,能避开大部分低级翻车。
5. 更深一层:AI 编程时代的团队规范该怎么演进
5.1 规范要从"给人看"变成"给人和 AI 都能看"
以前写编码规范,是写给团队工程师看的,可以写得比较宽松,靠人的理解力去补全。现在不行了,因为 AI 没有理解力,它只有模式匹配。规范必须写得更精确、更命令式、更可执行。
这意味着团队规范文档要做一次升级:把"建议""尽量""推荐"这类模糊词,换成"必须""禁止""统一"。把抽象原则,换成具体示例。把散落在各处的约定,集中到一个 AI 能读的文件里。
5.2 把"像不像"变成可检查的指标
"像不像"听起来很主观,但其实可以拆解成可检查的维度:命名规范符合率、异常处理规范率、统一返回使用率、注释质量、分层合理性。这些都可以通过静态分析加人工抽查来量化。
我们组现在的做法是,每次 AI 生成的代码合并前,抽查几个维度打分。分数低的,说明规则文件需要补充。这样规范本身也在迭代。
5.3 人的价值往哪走
AI 把"写出来"这件事的门槛拉得很低,但"写得像团队写的"这件事,反而更依赖人了。因为判断"像不像"需要的是对团队历史的了解、对业务上下文的理解、对代码审美的把握,这些恰恰是 AI 最缺的。
所以我的判断是,未来团队里最值钱的工程师,不是写得最快的,而是最懂"我们组该怎么写"的。他们能把团队的隐性知识显性化,变成 AI 能用的规则和示例。这件事,AI 短期内替代不了。
6. 我个人的几条实操心得
用 AI 写代码这一年多,踩的坑不算少,总结几条最实在的。
第一条,别指望一次到位。AI 生成的代码,第一版永远只能当草稿。把它当成一个手速极快但不懂你们组规矩的实习生,你的角色是带教,不是甩手掌柜。
第二条,规则文件要持续维护。每次发现 AI 又犯了同样的错,就往规则里加一条。三个月下来,这个文件会变成你们组最值钱的资产之一。
第三条,标杆示例比规则管用。与其写十条规则,不如给一个写得好的类让 AI 照着抄。人对例子的模仿能力,AI 也有。
第四条,别让 AI 碰核心业务逻辑的最终版本。它可以帮你写骨架、写工具方法、写测试,但涉及核心业务规则的地方,最后一定要人来定稿。因为 AI 不懂你们的业务为什么这么设计,它只会按最通用的方式实现。
第五条,定期回头看 AI 生成的代码有没有"沉淀"成新的技术债。AI 写得快,容易让人放松警惕,结果一堆风格不一致的代码悄悄进了主干。定期做一次代码风格巡检,很有必要。
最后分享一个小技巧:如果你不确定 AI 生成的代码像不像,把它和你们组最近合并的三个 PR 放一起,让另一个同事盲猜哪个是 AI 写的。猜得出来,说明还得改;猜不出来,基本就过关了。这个方法比任何规则都直观。