过去半年,我所在的团队开始把一大部分重复性编码任务交给 AI 完成,包括接口对接、数据清洗、前端表格页,甚至是重构历史遗留模块。功能完成的速度确实快,但 Code Review 的时间却肉眼可见地变长了。原因很简单:AI 生成代码的“可读性”经常不在线。它不是写错,而是写得让人看不懂——命名随意、函数冗长、上下文逻辑纠缠在一起,评审时你甚至需要先把代码重写一遍才能确认它是否符合需求。这篇文章不打算教你怎么挑选 AI 工具,而是想系统梳理一下:如何让 AI 生成的代码更容易理解、审查和维护。我踩过的坑、总结出的提示词写法、以及拿到初稿后的重构套路,都会写在下面,希望能帮你把 AI 编码真正接入到团队工作流里。
1. 先拆解问题:AI 生成代码最常见的三种“机器味”
如果你做过几十次 AI 代码审查,一定会发现那些“能跑但难读”的代码并不是随机犯错,而是很有规律地栽在几个固定地方。先把这些“机器味”摆到台面上,后面才知道怎么治。
1.1 命名空间里的抽象垃圾
我审查 AI 生成的 diff 时,第一眼永遠是变量名。模型特别喜欢产出data1、result2、temp、info这类名字。不是说不能用temp,但在一个函数里同时出现temp、temp1、temp2,或者用data代表“从接口拿到的用户列表”,又用另一个data代表“页面表单提交的字典”——这就是灾难。
有一次 AI 帮我们写一个数据同步脚本,核心变量分别叫a、b、c,然后c在函数中间被重新赋值成完全不同的东西。功能跑通了没错,但审查时我根本不敢点“通过”。因为我无法从代码本身判断b到底是“上次同步时间”还是“同步条数”。可读性差,本质上是在逼读者去“考古”——你得顺着上下文一点点推断原作者想干什么。
命名问题还有一个隐蔽变体:反义命名。is_not_disabled、should_skip_delete这种双重否定名字,逻辑上没错,但阅读时每个人都要多做一次布尔转换。AI 并不会主动考虑“读者心智负担”,它只会在训练数据里挑选出现频率高的表达方式。
1.2 一个函数干三件事
“每个函数只做一件事”是工程师挂在嘴边的话,但 AI 不会自动遵守。模型更倾向于生成流水账式函数:先初始化配置,再遍历数据,中间顺手更新数据库,最后拼接返回结果,甚至还在里面加了一段日志输出。
这种函数的共同特征是:行数很长,嵌套很深,多个if分支互相纠缠。我曾经审查过一个 200 行的 Python 函数,它同时负责解析配置、调用第三方接口、维护缓存、写审计日志。AI 之所以这么写,大概率是因为它把“正常人类的实现方式”理解为“从上往下堆步骤”,而一个典型的中级工程师照着需求文档写,也容易写出这种代码。问题在于,这样的函数一旦出现 Bug,定位成本会成倍上升:你无法快速确定是解析出错、缓存出错、还是日志写入阻塞导致接口超时。
更麻烦的是长函数还会让“复用”变成奢望。另一个需求也想用其中的缓存更新逻辑,但因为你没法安全地“提取”出那一小段,只能复制粘贴。AI 生成代码本来就是要压缩开发时间,结果这种结构反而制造出大量重复代码,为后续维护埋雷。
1.3 注释在讲另一个版本的故事
第三类机器味是注释和代码“脱节”。AI 非常擅长生成模板式注释:// 获取用户列表、# 初始化数据库连接。这类“是什么”的注释,对于没有上下文的新人几乎毫无价值。更糟的是,AI 有时会根据过时的推理生成注释,而代码执行路径已经被它自己改过了,最终注释和真实行为不一致。
我还经常在 AI 生成代码里看到空的TODO、无用的import、以及被注释掉的整段旧逻辑。如果人写代码留下这些东西,多少会被提醒清理;但 AI 生成时会忠实地把训练语料里的坏习惯“搬运”过来。审查者面对这些噪声时,相当于在信号里找信号,严重拖慢速度。
为了让你对这三种问题有个直观印象,我整理了一张简单的对照表:
| 机器味 | 典型表现 | 审查代价 | 修复方向 |
|---|---|---|---|
| 命名随意 | data1、temp、result2、双重否定布尔 | 读者需要反复跳转上下文 | 用语义化命名,能表达“是什么”和“为什么” |
| 函数臃肿 | 一个函数 200 行,多分支多职责 | 难以定位 Bug,无法复用 | 按职责拆分,控制圈复杂度 |
| 注释脱节 | 模板注释、无用 TODO、死代码 | 注释误导,噪声淹没逻辑 | 重写为“为什么注释”,删除无效内容 |
如果你也经常在审查 AI 代码时头疼,大概率是这三种机器味同时出现了。
2. 模型为什么总写出难读的代码:目标、视野与训练数据的合谋
很多人以为 AI 写得难读是因为“还不够聪明”。从工程实践看,这更像是一系列结构性原因叠加的结果。理解这些原因,你才能设计出有效的提示词,而不是反复在 Review 里骂模型。
2.1 模型优化的目标不是“可维护性”
大语言模型的训练目标是预测下一个 Token 的概率。它选择一段代码,是因为这段代码在概率上“更像人类写的”,而不是因为它“更容易被别人维护”。这两个目标之间存在本质偏差。
人类代码库里大量存在的是“当时能用就行”的一次性脚本、Stack Overflow 上的最小示例、以及个人项目里的快速实现。这些代码在统计分布上非常常见,所以 AI 很容易学到它们的形态。你希望 AI 写出结构清晰、命名讲究的代码,但它学到的“标准答案”恰恰是大众平均水平,甚至低于平均值。
这就解释了为什么仅仅在提示词里写“保持代码可读性”,效果往往很差。模型听不懂这种抽象口号,它更擅长模仿具体的输出格式。
2.2 上下文窗口带来的局部视野
工程师写一个模块时,脑子里通常装着整个系统的边界:入口参数、数据库表结构、调用方约定、错误处理策略。但模型在生成时,一次只能处理窗口内有限的 Token。窗口越长,注意力越容易被稀释,越到后面的代码就越容易跟前文脱节。
典型情况是:AI 在文件前半部分定义了一个叫users的变量,到文件中间已经忘了它代表的是“从数据库查出来的用户记录”,于是把新的返回结果也塞进同一个users,到后半段再出现时,变量类型已经悄悄变了。这种“局部视野”带来的不一致性,在长文件、长函数里尤其明显。
所以一个很实用的经验是:不要指望 AI 一次性生成一个 500 行的完整模块。让它分段生成、控制每次输出范围,可读性通常会有明显提升。
2.3 训练数据里混入大量“示例级”代码
AI 的训练语料来自公开代码仓库、技术博客、问答社区。这些地方的主体内容是什么?是示例代码、教学代码、个人脚本。它们的特点是:强调“跑通”,忽视“维护”。你在教程里更常看到a = 3; b = 4; print(a + b),而不是一个带完整类型标注、错误处理、边界测试的生产级模块。
模型并不真正理解哪些代码是“生产环境标准”,哪些只是“演示片段”。在它眼中,仓库里提交的代码和问答里的代码片段都是合法样本。于是生成结果里偶尔会出现测试残留、无意义变量、缺少空值判断等“业余感”,也就不奇怪了。
想对抗这一点,光靠模型自身很难,必须由你把生产级标准注入到提示词里。
2.4 缺少“评审打回”的反馈回路
人类工程师的代码质量之所以能提高,很大程度靠的是 Code Review。你提交的代码被同事打回过几次,下一次你自然会注意命名和拆分。但 AI 目前的工作模式通常是“一次性生成,一次性交付”,没有经历过“被要求重构”的反馈循环。
它不会在生成之后问自己:这个函数是不是太长?这个变量名别人能看懂吗?这里的异常处理是不是吞掉了错误?这些思考是审查者视角,而模型默认站在“生成者”视角,只考虑当前这一步写什么。
所以,把“审查视角”前置到提示词里,是提升 AI 代码可读性性价比最高的一步。我接下来要讲的,就是把这一步落地的具体做法。
3. 把可读性条款写进提示词:从“生成代码”到“生成可审查代码”
想让 AI 一开始就输出可读性合格的代码,不要指望靠运气,也不要在生成之后再花大量时间返工。正确的做法是,在提示词阶段就把可读性标准“焊死”。
3.1 把“可读性”翻译成机器能执行的具体规则
“请写一个可读性好的函数”这句话基本无效。模型不知道你的团队标准是什么,它只会按照训练分布里最常见的“可读性”来写。你需要把它翻译成一条条可以被检查的规则。
我的常用做法是,把规则拆成四类:
- 命名规则:变量名必须体现业务语义,禁止
data1、tmp这类无意义命名;布尔变量用is/has/can开头;禁止双重否定。 - 函数规则:单个函数不超过 30 行,条件嵌套不超过 3 层,一个函数只承担一个职责。
- 常量规则:禁止魔法数字,所有业务数字必须定义成具名常量或枚举;字符串必须使用常量,不允许直接散落在逻辑中。
- 注释规则:只写“为什么”不写“是什么”;删除模板式注释和无效 TODO。
把这些规则原样贴在提示词里,效果远好于一句“要可读”。你会发现模型输出的代码形态会明显改变,因为它确实有能力模仿你给出的格式,关键在于你有没有给出明确的格式约束。
3.2 给一个差示例和好示例,比说教管用
我做过对比实验:同样一个需求,一组提示词只写“注意可读性”,另一组提示词贴了一个“好代码示例”。结果第二组的可读性评分明显更高。模型本质上是在做模式匹配,你给它的示例越具体,它越能复制示例里的风格。
实际写提示词时,我会特意放一个“差代码”和“改进后代码”的对照,并用一两句话说清楚改进点:
下面是同一功能的两种实现。 差实现: - 变量名无意义 - 一个函数 40 行 - 魔法数字散落 好实现: - 命名语义清晰 - 单一职责,每个函数控制在 15 行内 - 业务常量集中定义 请按照“好实现”的风格完成以下需求:这样做的原理很简单:Few-shot 示例提供了明确的风格锚点。模型不需要理解“可读性”的抽象含义,它只需要对齐你给出来的具体输出形态。
3.3 分两步生成:先出设计,再出代码
如果你让 AI 一口气生成一个完整模块,它极容易陷入局部视野,把前文的设计原则忘光。更稳妥的方法是让生成过程分成两步。
第一步,让 AI 先给出设计方案:文件清单、每个函数的名字和职责、关键数据结构、输入输出定义。你在这个阶段就能检查命名和结构是否合理,不合理的地方直接在方案阶段改掉。
第二步,确认方案后,再让 AI 按方案逐段实现代码。这样每一步的上下文都聚焦在当前的一个函数上,模型的局部视野问题可以被明显抑制。
我在实际项目中用这套“两段式”提示词之后,Review 返工率至少下降了一半。因为命名和结构在方案阶段已经被我把过关,生成阶段只需要谈实现。
3.4 我常用的 Prompt 模板
下面贴一套我自己在项目里反复用的 Prompt 模板,你可以直接复制后修改业务部分:
角色:你是一名资深软件工程师,代码风格注重可读性和可维护性。 任务:根据需求实现功能,并输出完整的可审查代码。 硬性要求: 1. 所有变量、函数、类名必须使用语义化命名,禁止无意义缩写。 2. 函数控制在 30 行以内,单个函数只做一件事。 3. 禁止魔法数字,所有业务常量定义在文件顶部或常量模块中。 4. 注释只解释“为什么”,不重复“是什么”。 5. 不生成无用 import、死代码、TODO 占位。 6. 错误处理必须显式,不允许静默吞掉异常。 输出格式: - 先列出本次实现的文件结构和核心函数列表 - 再逐段输出代码 - 最后用 3 句话说明你的设计思路和潜在风险点注意最后一条:要求 AI 输出“设计思路和潜在风险点”。这会让模型在生成时带一点审查者视角,很多可读性问题在源头就被抑制了。
4. 拿到初稿之后的五个重构动作:从“能跑”到“能被看懂”
即使提示词做到位,AI 的初稿也不可能百分之百达到可读性标准。关键是你要有一套固定的重构动作,像流水线一样快速过一遍。下面五个动作是我每次审查 AI 代码时都会做的,顺序也基本固定。
4.1 动刀命名:把 temp 改成有语义的名字
命名是对可读性影响最大、成本却最低的一步。我会先扫描函数内所有变量名,问自己一个问题:光看名字,能不能猜出这个变量的含义和生命周期?
举一个实际的例子。AI 生成的一段缓存处理代码:
temp = cache.get(user_id) if temp is not None: data = temp else: data = fetch_from_db(user_id) cache.set(user_id, data)temp和data到底是什么,读者需要读完 5 行逻辑才能猜。改成这样:
cached_profile = cache.get(user_id) if cached_profile is not None: return cached_profile profile = fetch_from_db(user_id) cache.set(user_id, profile)命名一变,逻辑关系立刻就清楚了。这个小步骤不需要任何复杂工具,纯粹靠审查时的自觉,但它能节省后续所有读者的大量时间。
4.2 拆分“作者函数”:一个函数只做一件事
遇到超过 40 行的函数,我的默认动作是把它拆开。拆函数不是单纯把代码块剪切到新函数里,而是要按“职责”找边界。
典型的信号是:函数里的代码可以分成“准备数据”“处理数据”“写入结果”三个段落,且每一段之间通过临时变量传递信息。这一步重构效果非常直接:
# 重构前:一个函数处理所有事 def import_orders(raw_file): raw = load_raw_file(raw_file) parsed = [parse_line(line) for line in raw] deduped = deduplicate(parsed) validate_orders(deduped) save_to_db(deduped) send_notification(len(deduped)) # 重构后:每个函数各自独立,主流程一目了然 def import_orders(raw_file): orders = parse_orders_from_file(raw_file) orders = deduplicate_and_validate(orders) save_to_db(orders) notify_import_result(len(orders))拆分后的主函数像目录一样,读者一眼就能看到整个流程的骨架。想深入看细节,再逐个跳进子函数。认知负担完全不是一个量级。
4.3 消灭魔法数字:常量与枚举
AI 代码里最常出现的维护隐患就是魔法数字。比如:
if retry_count > 3: alarm()这个3是代表“最大重试次数”,还是“连续失败次数阈值”?如果换个业务场景,应该改哪里?AI 不会替你思考这些问题,它只会把数字原样放在判断条件里。
我的处理方式是把所有业务数字提为命名常量,或者放进枚举:
MAX_CONNECT_RETRY = 3 if retry_count > MAX_CONNECT_RETRY: trigger_alarm()不要小看这一步。常量集中定义后,你只需要检查常量名,就能理解整个文件的业务规则。魔法数字看起来是小问题,实际上一旦业务变化,排查成本会成倍上升。
4.4 重写注释:留 Why,删 What
AI 生成的注释大多属于“墙壁纸”类型:# 获取数据、// 循环遍历。这类注释对理解代码没有任何增量信息,并且会因为代码更新而过时。
我会保留两类注释:解释“为什么这么写”的注释,和跨层级的“设计意图”注释。比如:
# 这里不能直接清空表,因为上游可能还有未落库的引用记录 clear_staging_table(with_check=True)这种注释是真正有价值的。至于# 调用接口获取用户列表这种废话,直接删掉即可。代码本身已经很清楚地表达了这个动作。
还有一个基本原则:如果注释内容需要经常变动,那说明代码还没有抽取出合适的抽象。好的注释应当指向稳定层,而不是追逐实现细节。
4.5 收敛状态与副作用:能纯函数就纯函数
AI 很习惯写“有状态”的函数:改全局变量、写日志、更新缓存、操作数据库,全混在一起。副作用越多,函数越难测试,也越难看懂。
审查时我会先问:这个函数是不是可以直接接收参数、返回结果,而不依赖外部状态?如果是,就优先写成纯函数。比如:
# 重构前:依赖外部状态 total = 0 def calc_revenue(items): for item in items: total += item.price return total # 重构后:纯函数,输入输出一目了然 def calc_revenue(items): return sum(item.price for item in items)纯函数最大的好处是,读者不需要追踪“这个变量在哪被改过”的全局历史。你的函数是黑盒子,输入明确,输出明确,审查自然轻松。
5. 把可读性变成团队习惯:审查清单、评分卡与 AI 辅助闭环
一个人能靠自觉把可读性做好,但团队里的代码质量要想稳定提升,需要把“可读性”从个人偏好变成可执行、可度量的流程。这也是我最后想展开的部分。
5.1 把“感觉”变成“清单”
“这段代码看起来不好读”是主观感受,无法指导新手审查者。更有效的做法是把可读性标准拆成一张硬性清单,每条都可以回答“是”或“否”。
我团队现在用的核心清单包括:
- 所有命名是否表达业务语义?是否存在
data1、tmp、x这类名称? - 每个函数是否明显只做一件事?是否超过 30 行?
- 是否存在超过 3 层的条件嵌套?
- 业务数字和字符串是否已定义为常量?
- 注释是否解释了“为什么”?是否存在废话注释、死代码、无用 import?
- 函数是否尽量保持纯函数?副作用是否集中在边界层?
这张清单不需要很复杂,但它把“可读性”具象成了可以逐条检查的项目。审查者照着清单走,就不会因为“说不上哪里不对”而草率通过。
5.2 给代码可读性打分
如果想让可读性持续提升,建议在 Review 时顺手打一个分。我用的是一张五分制评分卡:
| 维度 | 1 分 | 3 分 | 5 分 |
|---|---|---|---|
| 命名 | 大量无意义命名 | 基本语义化但有歧义 | 命名能直接表达意图和边界 |
| 函数结构 | 单个函数 100 行以上 | 部分拆分但职责混杂 | 纯函数优先,拆分干净 |
| 注释价值 | 模板注释为主 | 有解释但部分过时 | 只保留有价值的 Why 注释 |
| 常量与魔法值 | 大量魔法数字 | 部分常量化 | 所有业务参数统一管理 |
| 一致性 | 风格前后矛盾 | 局部一致 | 与团队规范完全对齐 |
每次评分不需要很正式,但坚持记录之后,你就能看出 AI 生成的代码在哪个维度最常失分,然后针对性调整提示词。比如连续几周命名分都低,就在 Prompt 里多放几个好的命名示例。
5.3 让 AI 在生成代码时顺便自评
除了人工打分,还可以让 AI 自己先做一轮“可读性自检”。这个方法在上面的 Prompt 模板里已经埋了伏笔:要求 AI 在输出代码后,用几句话列出它的设计思路和潜在风险。
实践下来效果不错。模型在自检时会把注意力放到异常处理、边界条件、命名一致性上,相当于在生成阶段加了一层轻量的 Review。你拿到的代码,已经是“被 AI 自己预先审查过一遍”的版本。
更进阶的做法是,要求 AI 输出代码的“审查说明”,比如:
- 每个函数分别负责什么
- 可能的风险点在哪
- 如果需求变化,应该先修改哪个函数
这个流程会让 AI 的思维组织更接近“可维护代码”应该有的样子。虽然不能完全替代人工审查,但对提升初稿质量帮助极大。
5.4 在 CI 里让机器先读一遍
最后,可读性不应该只依赖人。很多可读性问题其实可以被工具自动发现。在 CI 里接入静态检查和复杂度度量,成本不高,收益却很稳定。
常用的组合包括:
- 圈复杂度检查:超过阈值直接构建失败
- 单函数行数限制:超过行数打警告
- 命名规范检查:强制 camelCase、snake_case 等统一风格
- 重复代码检测:发现 Copy-Paste 时在 Review 中高亮
- 无用依赖和死代码检查:清理 AI 留下的垃圾
把这些工具跑在 AI 生成代码的提交上,就等于在最前面加了一道自动过滤网。人的审查精力,可以用来关注那些工具发现不了的问题,比如设计合理性、业务逻辑正确性。
工具和清单配合使用后,团队里已经形成了一套闭环:AI 生成初稿 → 自评 → 静态检查 → 人工审查 → 打分 → 把失分点反馈回提示词。我自己的体会是,经过两三周的迭代,AI 生成代码的“机器味”会明显下降,Review 不必再像从前那样逐行重写。当然,可读性这件事没有终点,每次新需求、新语言、新框架都会带来新的挑战。但只要把标准、工具、反馈三条线建立起来,AI 生成代码的维护成本完全可以被压到可接受范围内。
最后分享一个我自己的使用习惯:凡是 AI 生成的代码合并前,我都会在本地跑一遍“可读性十五秒测试”——随便打开一个文件,不看注释,只看函数名和变量名,尝试复述这个模块的功能。如果复述不清楚,说明代码还不够直观。这个土办法花不了多少时间,却能帮你挡住很大一部分“能用但没法维护”的 AI 代码。希望这些经验也能在你们的项目里派上用场。