1. 先说现象:代码没毛病,但就是“味儿不对”
周一上午,组里的小王把分支推上来,让我帮忙 review。功能是个内部数据看板的导出模块,需求不复杂。我打开 diff,程序逻辑清晰,边界情况处理得比我预想的还细,测试用例也齐,一键跑通,覆盖率看着也顺眼。
但读了两百行之后,我总觉得哪里不对劲。命名风格不是我们的风格,注释语气不是我们的语气,连错误处理的套路都不是我们组的套路。我问小王这代码是哪来的,他很坦然:AI 写的,我让它把功能实现了,跑通了我就提交了。
我相信这种场景这两年已经在无数团队里发生过。代码没问题,编译能过、测试能跑、功能也完成了。但你往组里的代码库里随便翻几个文件,再对比这段“一跑就通”的代码,会发现它们像是两个不同团队产出的东西。更麻烦的是,这种“风格漂移”不是少数现象——越是用 AI 生成整段代码,漂移得越明显。
这篇文章我想认真聊聊这个问题。不是站在“AI 写代码不好”的角度唱反调,恰恰相反,我自己重度使用 AI 写代码,日增量相当可观。但正是因为用得多,我才意识到一个被绝大多数人忽略的核心问题:AI 写出来的代码默认是“大众脸”,而我们自己组的代码是有“基因”的。让 AI 写出来的代码不只跑得通,还得长得像“自家孩子”,这中间差着一整套方法论。
如果你也是团队里负责技术规范、做 Code Review、或者每天被组员的 AI 代码淹没的人,这篇文章应该能帮到你。
2. AI 的代码为什么天然带着一股“外人味”
2.1 模型的默认风格不是你的团队风格
先理解一个底层事实:大模型写代码,本质上是在做“高概率续写”。它从海量的公开代码库里学到了“大部分程序员会怎么写”——这个大部分,指的是 GitHub 上各类开源项目、教程、Issue 讨论、博客片段混合出来的统计数据。
这个统计数据长什么样?可以概括为“最大公约数风格”:主流框架的标准写法、教科书式的命名习惯、最常见的包名和 API 调用方式。比如生成一个 Python 函数,模型大概率会用def process_data(df):这种通用命名,而不是你们组统一的def transform_dataset(raw_df: pd.DataFrame) -> pd.DataFrame:。再比如,它大概率会把日志直接print出来,而不是走你们组统一的logger = get_logger(__name__)再logger.info("...")。
问题就出在这:你们的团队风格,恰恰不是最大公约数。它是你们在特定业务上下文里,经过多年踩坑、争论、妥协之后沉淀下来的“方言”。模型学不会你们的方言,除非你明确告诉它。
我见过很多团队把“AI 写的代码不像我们的”归结为玄学,其实一点不玄。这只是因为团队风格在模型的先验分布里权重太低,低到默认情况下根本轮不到它。
2.2 一篇代码里的隐性风格指标,逐个拆开看
风格不是一个抽象概念,它是具体到每一个字符走向的一组偏好。我总结了一下,AI 代码最容易露馅的地方有六处。
第一是命名。团队往往有自己习惯的缩写体系,比如req、resp、evt、svc,也有自己习惯的完整拼写偏好。AI 默认会走“半缩半全”的路线,比如request_data、event_handler,不丑,但不属于你们。
第二是项目结构。你们组可能约定业务逻辑统一放service/目录、数据访问统一放dao/目录,AI 生成的代码却总是倾向于把逻辑都堆在一个文件里,或者反过来生成一堆过度拆分的类。
第三是异常处理策略。有的组习惯“快速失败”,哪里出错哪里抛;有的组习惯“兜底降级”,出错返回默认值。AI 的默认策略是“能抛就抛,附带一个看起来合理的异常类型”,这个默认很可能跟你组的习惯背道而驰。
第四是注释风格。有的组只在复杂逻辑处写注释,有的组强制要求每个公开函数有 docstring。AI 默认会写不少“这种代码还用注释吗”的注释,比如# 循环遍历数据,让人哭笑不得。
第五是类型标注密度。有的组全量使用类型注解,有的组只在公共接口上标注。AI 默认会按训练数据里的宽泛分布来,导致你的审查者要在一半有类型一半没类型的代码里来回切换。
第六是测试风格。你们组可能习惯用 pytest 加 fixture,AI 生成的测试可能是 unittest 风格,或者干脆是“建一堆临时数据但不清理”的野路子。
2.3 风格漂移的真正危害:不是不好看,是成本放大
你要说风格不统一能不能跑?当然能跑。但这会让团队付出几笔隐性成本,而且每笔都是复利型的。
第一,认知负担增加。人脑读代码不是逐行编译,而是靠模式识别。你看到request_ctx.user_id,脑子里会自动弹出“这是从请求上下文拿用户ID”的语义。但看到x = get_data(),你的大脑就要多一步“这个 get_data 到底取的什么数据”的解析。团队代码每多一分风格漂移,每个人的阅读速度就慢一分,而这个速度是每天要重复支出的。
第二,Review 效率下降。我自己的经验是,review 一份风格陌生的代码,注意力会被“这写法怎么跟我们不一样”分散掉,真正该盯的业务逻辑漏洞反而容易漏过去。本来 20 分钟能看完的代码,可能要花 40 分钟,其中一半时间消耗在“风格辨别”上。
第三,代码考古困难。半年后这个模块出 bug,接手的人打开文件,发现风格跟周边的文件完全不同,会直接怀疑“这段代码不是我们组的人写的”。再翻 git blame,发现是你引入的。然后团队会因为这段代码展开一次没有结论的争执。这种内耗,完全可以通过统一生成风格来避免。
3. 把“团队风格”变成一段 AI 能读懂的输入
3.1 风格规范的本质是约束生成分布
前面说了,模型生成代码是概率选择。想让它的输出往你的团队风格偏移,思路其实就一条:把团队的风格偏好塞进模型的上下文里,改变它的概率分布。模型不是不知道你的团队风格,是它默认猜不到。你只要把规则表达清楚,它的输出会立刻靠近你想要的方向。
这里有个比喻我一直觉得很贴切:AI 就像一个经验丰富但性格随和的自由职业者,你直接说“帮我写个导出功能”,它会写他自己最顺手的那种代码;但如果你给一份《团队开发规范》外加两个参考文件,他会照着你的规矩来,而且执行得比大多数人类新人还稳定。差距就在:你有没有把规矩给到位。
3.2 实操第一步:把团队规范压缩成“风格锚点”
我们组做过一件很实在的事:把散落在 wiki、文档、口头约定里的风格偏好,压缩成了一份 60 行的《小组 AI 代码生成锚点》。它不需要覆盖所有规范,只覆盖那些“AI 最容易写岔”的条款。我摘录几段给你看,你可以对照自己的团队情况改造。
你是后端 Python 开发专家。请严格遵守以下团队代码风格: - 命名:变量用小写蛇形;类用大驼峰;布尔变量以 is_ has_ 开头;禁止使用 x, y, tmp 等含义不明的命名。 - 类型标注:所有函数入参和返回值必须标注类型;禁止在类型标注中使用 Optional[X],统一使用 X | None。 - 异常处理:业务层禁止裸抛 Exception;必须抛出带上下文描述的业务异常 BizError。捕获异常时禁止空 except;捕获后必须至少记录一条 warn 日志。 - 日志:统一使用 logger = logger_factory.get_logger(__name__);禁止 print。 - 注释:只对复杂逻辑写注释;禁止为代码行本身写废话注释。公共函数必须写 Google 风格 docstring,包含 Args/Returns。 - 返回风格:业务函数禁止直接返回 None 表示失败;统一返回 (result | None, error | None) 元组。 - 文件组织:一个文件只放一个类或一组强相关的函数;禁止在 __init__.py 里写业务逻辑。这份锚点文档现在是我和组员用 AI 写代码时的必带文件。实测下来,只要把它放进系统提示词或者项目上下文里,AI 生成代码的“自家人指数”至少提升七成。注意这里说的不是准确度,而是风格贴合度——函数名对了、注释类型对了、错误处理味道对了,Review 的人一眼扫过去就舒服很多。
3.3 实操第二步:用“参考文件”代替抽象描述
风格锚点能约束大方向,但有些风格是文字写不清楚的。比如“你们组的 controller 长什么样”、“目录结构怎么组织”、“数据库查询是走 ORM 还是原生 SQL”。这些事靠文字描述,AI 会理解得七零八落。
更好的办法是给 AI 一个“黄金样例”。我在实际操作中会在提问时附上一两个现有的、高质量的模块文件,告诉模型“按照这个文件的写法,实现一个同风格的新功能”。这个方法效果极好,因为模型对“模仿范例”的擅长程度远超“理解抽象规则”。
举个例子,我们组有个内部工具平台,前端是 TypeScript + React,后端的 Service 层有一套约定俗成的写法。过去我让 AI 直接写 service,每次都要改半天。后来我改成每次粘贴一个现有 service 文件作为参考,新写的代码几乎不用动。模型会主动模仿板块导入、函数命名习惯、返回结构甚至注释语气。
注意一个细节:参考文件不要选那种历史包袱重、兼容逻辑多、可读性差的文件。模型会把你给的丑陋写法也学过去。选一个你自己觉得“这就是我们组标准审美”的文件,当作风格模板。
3.4 实操第三步:把 Review 意见变成“回灌数据”
AI 写代码的一大优势是你可以对它反复提意见,而且它不会像组员一样不耐烦。我强烈建议你把 Review 时发现的风格问题整理成固定话术,在后续生成时直接回灌给模型。
比如我见过一个典型的场景:AI 生成代码时总是给内部函数也写 Docstring,而我们组的习惯是只给公开接口写。第一次发现时,我会在对话里追加一句“注意:内部函数不要写 Docstring,只有公开函数需要”。第二次它就会照做。这个“反馈-修正”链条比你想的更稳定。
更进一步,可以把每次 Review 中发现的高频风格问题追加到 3.2 的锚点文档里。三个月下来,那份文档会从 60 行涨到 120 行,而 AI 生成的代码会越来越像“我们组的人写的”。这个过程本质上是把团队隐性知识显性化,AI 只是强迫你把这些知识写下来的理由。
4. 不是让 AI 一步到位,而是在流程里加几个“风格关卡”
4.1 本地生成阶段的约束:用工具把风格“硬校验”住
光靠提示词让 AI 写得像自家代码,说到底还是概率游戏,总有漏网之鱼。要兜底,必须引入机器层面的硬校验。
这里的核心思路是:风格问题分为两类,一类叫“语义风格”,比如命名习惯、注释密度、错误处理策略,这类适合用提示词约束;另一类叫“机械风格”,比如缩进、引号、行宽、分号、导入顺序,这类完全没必要靠人和模型博弈,直接用格式化工具钳死即可。
我在团队里推的标准组合是:Python 用 Ruff + Black,TypeScript 用 ESLint + Prettier,Java 用 Spotless + Checkstyle。格式化工具统一跑一遍,机械风格就不存在“像不像我们组”的问题,因为每个人提交前都被强制格式化成同一个样子。这一步做完,AI 在这类风格上的发挥空间直接清零。
你可能觉得这是废话,但我见过太多团队从一开始就是“靠人自觉”维护格式统一,结果 AI 代码一多,光靠 review 根本盯不住缩进和引号的问题。格式化不是审美问题,是生产力问题。把机械风格从人的视野里挪走,review 的注意力才能集中在真正的业务逻辑和架构问题上。
4.2 提交阶段的关卡:Git Hooks 拦截常见风格漂移
格式化工具能管住“长什么样”,管不住“用什么 API、走什么结构”。这类深层风格偏好在团队里往往有明确约定,比如“所有数据库操作必须走 repository 层”“禁止在 controller 里直接调用第三方 SDK”“日期处理统一用 arrow 库而不是 datetime”。
这种规则,AI 提示词写得再详细也有疏忽,人也不可能每一行都盯。我的做法是:把最硬性的几条,写成自动化检查脚本,挂到 pre-commit 钩子上。
我这里贴一个我们组实际在用的 pre-commit 配置片段,它不是全量,但能说明思路:
repos: - repo: local hooks: - id: forbid-print name: forbid print() in service layer entry: python scripts/check_forbidden.py --pattern "print(" --path app/service language: system - id: forbid-bare-except name: forbid bare except entry: python scripts/check_forbidden.py --pattern "except:" language: system - id: forbid-direct-sdk name: forbid direct oss sdk call entry: python scripts/check_forbidden.py --pattern "import oss2" --path app/api language: system这三个检查看着简单,实际帮我们拦下了大量“看着跑得通、实则踩了团队红线”的 AI 代码。比如print(这条,AI 默认在函数里调试时会顺手来一个,而我们在生产环境统一走日志框架。过去这种问题要靠 reviewer 肉眼抓,现在机器直接拦。
4.3 流水线阶段:CI 里的风格质量门
Git Hooks 能拦住本地提交,但只能拦“主动安装了 hooks 的人”。组员如果用了git commit --no-verify跳过钩子,或者干脆没搭好本地环境,风格检查就形同虚设。所以最后一道防线必须放在 CI 流水线里。
我们在 CI 里加了一个极其简单但极其有效的 Job:拉分支代码后,跑一遍团队自定义的风格检查脚本集,把不符合规则的代码统一列出报告,如果检查失败,流水线直接标红,不允许合并请求。我管这个叫“风格质量门”,它把风格问题的拦截点从“人审”提前到“自动”。
实践中有个要点:CI 里的风格检查规则,宁可先少后多,别一次上太多。如果一开始规则太严格,组员会被吓到,产生抵触情绪。我建议第一批只放三条最不能妥协的硬约束(比如禁止裸异常、禁止 print、禁止绕过 repository 层),跑顺之后再逐步增加。
4.4 代码评审阶段:给“AI 代码”单独建一个 Review Checklist
Review 是流程的最后一站,也是争议最多的一站。我见过不少团队对 AI 代码的 review 态度两极分化:要么彻底不信任,每行都当同事写的代码来抠;要么过度信任,觉得“AI 写的至少能跑”,草草看过就合。这两种都不健康。
我自己的做法是,在常规 Review Checklist 之外,给 AI 生成的代码单独加一层清单。它不是否定 AI,而是把 AI 最容易出问题的地方专门列出来,让人单独过一遍。
重点检查这几项:
- 有没有幻觉出来的 API 或参数名?模型会用拼接的方式编造不存在的库函数,跑测都不一定发现,review 时留意那些“看起来很眼熟但你不确定”的调用。
- 有没有过度设计?AI 倾向于给简单功能套工厂模式、策略模式、装饰器,review 时多问一句“这里真的需要一个抽象层吗?”
- 业务规则有没有被“合理默认值”篡改?AI 会在你没定义边界条件时自己补一个听起来合理的默认行为,这个行为很可能不是产品想要的行为。
- 有没有复制粘贴式的冗余代码?AI 生成的一大特点是把相似逻辑复制多份而不是抽公共函数,发现这种要打回重写。
这个清单我会贴在 PR 模板的注释里,让提 PR 的人自己先勾选一遍。两个月跑下来,我发现组员对 AI 代码的态度从“跑通了就交”慢慢变成“跑通了还得检查它有没有乱设计”,这就是流程起作用了。
5. 我们组踩过的坑和排查实录
5.1 提示词写得很全,AI 却还是我行我素,怎么办
有组员跟我反馈过:规则写在提示词里了,AI 每次都答应得好好的,生成出来还是老样子。这其实是个经典误区:系统提示词里规则条目太多太杂,模型会“注意力稀释”,它记得住前面三条,后面几条就飘了。
我自己的排查办法是:把提示词里的风格规则压缩到最关键的 5 到 8 条,其余全部挪到参考文件里。规则越少,模型遵守得越稳。还记得我们组那份锚点文档吗,最初版本有 30 条规则,实测效果反而不如精简后的 12 条,因为我删掉了那些“AI 本来就不会写错”的废话规则,比如“变量名要有意义”——这类规则占注意力,却没有增量价值。
5.2 AI 生成的代码风格是“过度防御”,怎么往回收
另一个高频问题:AI 生成的代码总是带着一堆看起来很有道理的防御逻辑。比如每个函数开头先判if data is None: return None,每个字典取值都用dict.get()再判空,好像生怕用户把系统用崩。
这种风格不能说错,但它跟很多团队追求的“快速失败、前置校验、拒绝脏数据进入业务逻辑”的哲学是相悖的。防御逻辑散布在业务流程里,只会让代码主线被无关分支淹没。
我的处理办法是在风格锚点里明确加一条:“禁止在业务函数内部做冗余空值防御;数据合法性必须由调用方保证。确需防御时,必须在函数开头集中校验并抛出明确异常。”加上这条之后,AI 的输出立刻清爽很多。
5.3 注释像是从教科书抄的,怎么看怎么别扭
AI 默认的注释风格是解释“这段代码在做什么”,而团队的注释价值在于解释“为什么这么做”。比如# 统计数量这种注释,是 AI 的最爱,但对读者一点帮助都没有。我们组有经验的工程师写的是# 注意这里不能用 count(),因为需要包含未激活用户。
这个问题用提示词纠正比较难,因为模型对“注释风格”的感知远不如对“命名风格”敏感。我试过最有效的办法还是给参考文件,找一篇注释写得特别好的现有代码喂给它,让它模仿那种“为什么型注释”的写法,比写十条“不要写废话注释”都管用。
5.4 生成的目录结构跟项目不一致,怎么让它融进去
还有一次,AI 帮我生成一个消息推送模块,逻辑本身没有问题,但它默认把代码组织成了push_service.py一个文件,而我们的项目约定是分sender/、templates/、stats/三个子目录。这个差异 Review 时一眼就能看出来,但靠人工挪动结构,那 AI 的“省事”优势就全没了。
我的解决方案是:给 AI 提供项目目录树的截取,明确指定目标路径应该放在哪、周边有哪些文件。让模型理解到“它不是在真空中写一个新文件,而是在既有的代码生态里种一棵树”。AI 对目录上下文的感知能力其实很强,只是之前很少有人给它提供这部分信息。
5.5 AI 时代写代码,警惕“能跑”变成唯一标准
最后想单独说一个偏管理层面但越来越重要的问题:当 AI 使得“写出一段能跑的代码”变得极其廉价时,团队里会出现一种隐性的标准滑坡——只要能跑,就算完成。我观察到不少新人在使用 AI 后,对代码质量的要求在不知不觉中放松了。他们提交的代码不再是“我认为这段代码该这样写”,而是“AI 给我什么我看看差不多就交了”。
这不是 AI 的问题,是流程的问题。代码不光要能跑,还要能被下一个读它的人轻松理解,还要符合团队三年来的架构惯性,还要经得起几个月后的需求变更。这些东西,AI 不会替你操心,因为它是“最大公约数”式的聪明——平均水平的程序员怎么交差,它就怎么生成代码。
所以我一直跟组里说一句话:让 AI 帮你写代码可以,但你必须自己知道“好代码长什么样”。AI 帮你把打字加班的部分干掉了,你省下来的精力,应该花在更值钱的地方——审视设计、审视边界、审视它默认帮你做的那些“看起来合理”的决定。
一些我个人的经验和后续想法
这套方法我们组跑了大半年,现在 AI 生成的代码不说 100%,但起码八成以上的合并请求,你不看提交人,已经分不清哪些是 AI 写的、哪些是人写的了。这是我觉得最有成就感的事,不是因为“AI 被驯服了”,而是因为团队终于把“我们的代码风格”这件事从口头文化,沉淀成了机器能执行、模型能理解、新人能上手的实体资产。
如果你所在的团队也刚遇到“AI 写的代码一跑就通,但不像我们组写的”的问题,我建议不要急着抱怨模型,也别急着要求组员“少用 AI”。先花一个下午,把你们组那几个“看一眼就知道不是我们的人写的”点列出来,转成一份能直接贴给 AI 看的风格锚点,再配合一条 pre-commit 钩子管住最硬的红线。从这起步,你会看到改变比想象中来得快。
这个方向其实还能继续往下走:每个团队都应该建一份自己的“黄金样例集”,把那些值得让 AI 模仿的文件收集起来,随着时间更替,让 AI 真正成为带着团队基因的“数字新同事”。这大概是 AI 时代里,技术管理者最值得投资的一件事。