生成式 AI 在你代码库里表现不佳,原因有很多。但追根溯源,很可能是这么一个扎心的事实:你的代码库对 AI 不够友好。而更扎心的是,它对人也不见得友好。团队里缺乏一致的规范和最佳实践,代码库的可读性、可维护性和可扩展性自然是每况愈下。
这篇文章会有点长,核心就是想聊聊怎么打造一个对 AI 友好的架构,顺带也整理一下这些年我看到的团队协作实践。我们先从软件工程里一个很微妙的点开始说起。
引子 1:软件工程 - 团队实践优于最佳实践
说句实在话,软件工程里没有绝对的“最佳实践”。一个实践好不好,关键看它适不适合你的团队。在把 AI 拉进开发流程之前,很有必要把自己团队现有的那套工程实践先理一理。
代码命名规范的产生:构建统一的语言
就拿一个金融场景来举例。假设你们有个产品叫“稳享灵动慧利”,代码里该怎么命名它?
直接翻译:SteadilyEnjoyAgileWisdomAndBenefits
挑选重点:AgileBenefitController
拼音全称:WenXiangLingDongHuiLi
拼音首字母:WXLDHL
……
通常来说,方案1和2基本不会有人选。技术负责人会告诉你,别人看不懂——这不光是考虑新人或外包,你自己隔几个月回来看这段代码也是一脸懵。方案3和4反而是最常选的。所以你看,这事没有对错,团队内部能达成一致,就比什么都强。
代码检视在检视什么:维护团队最佳实践
拼音用多了,又会带来新问题。比如,“用户”被翻译成“YongHu”,福建的同事可能一不小心就写成“YongFu”。怎么避免?一个简单的办法就是代码检视。你指望 Sonarlint 之类的工具来纠偏?它可管不了这种“文化差异”。所以,代码检视在一定程度上解决了这个问题。
我们期望的代码检视,核心目标一般是这几个:
:命名、风格、注释这些,得统一。
:通用的逻辑模式、代码重用,该用就得用。
:代码改动得和业务逻辑对得上。
:逻辑错误、性能问题,能揪出来自然最好。
但说真的,想靠代码检视来发现所有潜在的 bug,挺难的。大多数人很难对业务逻辑了如指掌,更别提把所有边界条件都考虑到了。而且评审的时间也有限,不可能覆盖所有代码。所以,代码检视的初衷,更多还是为了让团队在规范和最佳实践上步调一致。
遏制面条式代码:分层筑结构,内聚定边界
我们常常在微观层面抠代码质量,却不知不觉间忽略了宏观架构的“腐化”,结果就是可维护性越来越差,最终陷入“面条式代码”的泥潭。这背后的原因,很大程度上是我们没有从结构层面去思考:新代码应该放在哪?相关的逻辑要怎么组织?代码放错了地方,毫无章法地增长,人看不过来,AI 同样也会一脸懵逼。
解决之道在于构建清晰的结构,这主要靠两个相辅相成的策略:
:在宏观上,通过分层(比如表现层、业务逻辑层、数据访问层)来划分系统的主要职责区域。这强制性地把不同类型的关注点隔离开(界面逻辑不该和数据存储逻辑混在一起),给代码库提供了一张高层级的导航图。
:在微观上,也就是每一层内部,强调内聚性。功能上紧密相关的代码模块,应该聚合在一起。比如,所有和“用户认证”相关的逻辑都集中管理,而不是东一块西一块。这样,每个模块的功能才够单一、够明确。
当现有的设计已经不够用了,重构就成了我们的改进手段——把不同职责的代码,按照逻辑关系,清晰地划分到不同的“层”或者“模块”中去。
引子 2:尝试并理解 AI 代码生成的限制要素
现在,我们用 AI 编程,通常有几种方式:
:比如 ChatGPT,需要手动输入上下文。
:直接在 IDE 里生成代码,能选择或自动带上上下文。
这两种模式的核心区别,就在于是否带有用户代码的上下文信息。
理解基于上下文的 AI 代码生成:龙生龙,凤生凤
用过 AI 插件的代码补全功能,你肯定能感受到:它生成的代码质量,比网页聊天那种要强太多了。拿 AutoDev 示例项目里的 BlogController 来说。如果我们想生成一个 delete blog 的 API 接口,在 getBlog 方法后面加个注释:
@RestController
@RequestMapping("/blog")
public class BlogController {
BlogService blogService;
public BlogController(BlogService blogService) {
this.blogService = blogService;
}
@ApiOperation(value = "Get Blog by id")
@GetMapping("/{id}")
public BlogPost getBlog(@PathVariable Long id) {
return blogService.getBlogById(id);
}
// delete blog by id
然后触发 AI 补全,它生成的代码可能是这样的:
@ApiOperation(value = "Delete Blog by id")
@DeleteMapping("/{id}")
public void deleteBlog(@PathVariable Long id) {
blogService.deleteBlogById(id);
}
看到了吗?那个 @ApiOperation 就是 AI 根据你前面代码的规范“照葫芦画瓢”生成的。而 deleteBlog 这个命名,也自然受到了你前面命名规范的影响。所以说,你基于别人的代码去修改时,会发现 AI 的质量不尽如人意,原因就在这里。好的代码会让 AI 生成更好的代码;反过来,屎山也更容易让 AI 产出屎山。当好代码和坏代码混在一起时,那就只能看运气了。
如果你想让 AI 生成一大段代码(比如多个方法),但缺乏足够的上下文,补全式模型的效果就会大打折扣,它可能会生成一些不存在的假方法。现在的补全式模型为了效果更好,往往会过度拟合,这也导致它们难以生成那种大段的、有逻辑关联的代码。
理解好的问题表达的重要性:欲速则不达
在生成式 AI 时代,你提问的质量,直接决定了生成代码的质量。我们经常遇到两种情况:
:比如“实现一个用户管理功能”,AI 很可能生成不完整或根本不符合预期的代码。
:总想用一两句话就搞定所有事,结果生成的代码里藏着不少坑。
给 AI 下指令,比如让它实现一个用户管理功能。它首先得能从当前的代码库里理解什么是“用户”,什么是“管理”,然后才能相对准确地生成代码。否则,AI 就只能凭自己的理解瞎猜,质量自然好不到哪去。
所以,我们得先定义一下:在 AI 时代,什么样的提问才算好问题?当然,这个可以交给 AI 自己去研究,比如用 Google DeepResearch 来做。
通过一些初步探索,我发现有效的提示词通常包含几个关键要素:清晰的任务指令、充分的背景信息、具体的细节描述、明确的角色设定、期望的输出格式,以及合适的语气和风格。提供范例和设定约束条件也是很重要的技巧。
总结一下,有效的提示词需要清晰和具体,比如:
:清晰地告诉 AI 你要它做什么(比如“写一个 Python 函数”、“重构这段 Ja va 代码”、“生成 SQL 查询”)。避免模糊或模棱两可的表述。
:AI 需要上下文才能理解它要完成的任务是什么。这包括:
:项目的目标、使用的技术栈(语言、框架、库版本)、架构模式等。
:相关的代码片段、接口定义、类结构或函数签名,让 AI 了解当前的实现方式和约束。
:如果任务涉及特定业务领域,提供相关的术语、规则或概念(比如 DDD 中的通用语言)。
:详细说明需求,包括输入、输出、预期行为、边界条件或错误处理。比如,别只说“计算阶乘”,改成“写一个 Python 函数 factorial,接收一个整数 n,返回它的阶乘。如果 n 小于 0,返回 None”。
把这些提示词工程技巧用好,特别是充分利用现有代码库的上下文和领域知识,能显著提高 AI 生成代码的准确性,让它更好地融入项目。
理解受限的小参数理解能力:巧妇难为无米之炊
现在的 AI 编程工具里,通常会集成很多不同的模型,比如补全模型、快速应用模型、聊天模型、推理模型等等。这些模型的参数大小不一,有的高达 175B,有的却只有 6B。通常来说,参数越大,模型的理解能力就越强。有传闻说,早期的 Copilot 应用里,补全采用的是约 12B 的 Codex 模型,而聊天用的则是约 175B 的 GPT-3.5 模型。当参数量变小,比如采用 3B 的补全模型时,模型就很难理解复杂的需求,对中文的理解能力也会变得非常有限。这时候就可能出现,你用很简单的中文描述,模型却搞不懂的情况。
所以,如果你想实现复杂的需求,就得借助参数大的模型。模型越大,生成质量越好,但速度也越慢。针对不同的场景,需要采用不同的生成策略。
构建 AI 友好编程:从实践到模式
想用一篇文章把 AI 友好型架构讲透,不是件容易的事。我尽量用模式化的方式去抽象,这样你在不同场景下也能灵活运用。这些模式的目标,就是创建一个既便于人类协作,又能被 AI 高效理解、分析和改进的代码环境。
模式:领域知识丰富上下文与问题定义
:用户的描述往往模糊不清,AI 很难理解其意图,生成的代码质量自然不高。
实践方式:领域语言与提示词工程优化用户输入
:应用领域驱动设计的原则,把复杂的业务领域知识显式化、结构化,为 AI 提供更丰富、更精确的上下文信息,从而提升它对需求的理解和代码生成的质量。
:在 AutoDev 中,我们通过 AI 生成领域术语表。用户输入内容后,可以选择优化提示词来丰富上下文信息。
:通过静态代码分析拿到所有类名、函数名信息,交给模型分析、理解和生成,最终输出一个 domain.csv 文件,包含中英文和描述信息。
(可选)。
:用户在 AutoDev 输入框内点击“优化提示词”,系统就能把术语表中的上下文信息带入需求中。
:用户的需求是“实现一个用户管理功能”。结合领域知识,利用 DDD 的通用语言,我们可以把它重新定义为:实现一个“用户账户管理”的功能,包含“用户注册”、“用户激活”、“用户资料更新”等。通过领域术语来丰富需求描述,提供更清晰的上下文信息。
再详细一点的定义示例:“(角色:资深 Ja va 工程师)使用 Spring Boot 框架,实现一个‘客户账户管理’限界上下文的核心功能,包括:(1)处理 POST 请求 /customers/register 用于客户注册,接收包含 'name', 'email', 'password' 的 JSON,验证 email 格式和密码强度,成功后保存至数据库并发布‘CustomerRegistered’领域事件。(2)处理 POST 请求 /accounts/activate/{token} 用于账户激活……” 这种方式提供了更清晰的业务术语、技术约束和具体行为描述。
实践方式:将需求工程应用于 AI 交互
:在 IDE 里连接在线的需求工具(如 Jira、Confluence、Notion),将用户的输入转化为检索条件,调用在线工具进行检索,再把用户的输入转换成带有完整需求上下文信息的提示词,交给 AI 处理。
:在 Cursor、Copilot、AutoDev 等工具中,可以通过自定义 Agent、MCP 等工具,让用户接入自己的需求工具。
用户构建自己的 Jira MCP 服务器。
将 MCP 服务器配置到项目中使用。
将检索到的 Jira Issue 的摘要、验收标准、附件设计文档等内容,按照“领域术语表 + 需求模板”的格式汇总,交给模型处理。
:用户输入“添加支付提醒功能”,工具可以提示“在 Jira 中找到 2 条相关 Issue:PAY-102、INVOICE-58,是否将它们的描述和验收标准作为上下文?”由用户确认是否关联,并进一步优化提示词。
模式:基于项目知识与规范的智能生成
:AI 编程助手通常缺乏对特定项目的深入理解,包括编码规范、技术栈选型、架构设计、领域术语、历史决策以及团队约定。如果开发者不手动提供这些上下文,AI 生成的代码往往会过于通用,不符合项目标准,甚至与现有架构冲突。反复手动提供完整上下文,效率低下且容易遗漏。
:构建一个系统性的框架,让 AI 助手能够自动、持续地获取并利用项目特定的知识和规则,从而时刻以项目实际情况为准。
实践方式:借助 Project Rule 预先定义项目上下文
:每次与 AI 交互时都要手动提供编码规范、技术栈、架构模式这些项目上下文,效率很低,也容易漏掉关键信息。
:利用现代 AI 编码助手提供的“项目规则”或“自定义指令”功能,预先定义好项目上下文信息,让 AI 能够自动加载并遵守这些规则。这需要结合有效的知识捕获和管理策略,并通过 RAG 技术将知识提供给 AI。
:利用 AI 编码助手提供的内置配置功能。
:在项目特定位置(如
.cursorrules,
.github/copilot-instructions.md, IDE 设置)创建配置文件。其内容可能包含:
:用自然语言描述:
:如命名约定、代码格式化标准。
:指定使用的框架、库版本,禁用或推荐特定库。
:如微服务、事件驱动,DDD 限界上下文划分,SOLID 原则的特定应用。
:内部/外部 API 的调用方式、认证要求、数据格式。
:标准异常处理流程、日志级别与格式。
:项目的核心业务术语及其含义。
:支持全局、项目级、特定文件类型/路径的规则设置,甚至动态激活规则。
把这些工具的规则配置功能用好,团队能显著提升 AI 代码助手的实用性,让它更好地融入项目的实际开发流程。
实践方式:持续的项目知识捕获与注入
:让 AI Agent 能够利用项目中不断积累的动态知识,以及用户偏好,理解更深层次的背景、决策逻辑和隐性经验。
:利用 AI Agent 的 "Memories" 功能,将关于工作区和用户偏好的关键信息持久化存储在本地。系统会自动学习并更新这些记忆,用户也可以手动管理。这些记忆随后作为 RAG 的关键知识源,在处理请求时被检索出来,跟当前上下文一起注入给大模型。
:比如 Augment 的 Memories 会记录用户在对话中的关键细节,保存为
augment.memories 文件,供后续对话使用。以下是之前记录的一些用户偏好示例:
用户希望使用 jetBrains jewel 仓库作为修复项目错误的参考。
只使用 JetBrains JewelUI 组件,而不是 Material 组件。
用户希望使用 JetBrains jewel 中的发送按钮图标,而不是重启图标。
:记忆可能被污染或损坏,自动学习可能出错;透明度不足,用户难以完全理解记忆内容及其影响;管理开销,需要用户主动监控和清理;安全风险,本地文件可能成为提示注入的目标。
小结:手动的知识获取
需要强调的是,“项目知识驱动的上下文注入”这个模式能成功实施,高度依赖于团队持续进行有效的知识管理。这包括对
(如项目文档、知识库、代码注释、设计规范)的系统性维护,以及对
(蕴含在团队经验、历史决策、讨论沟通、开发实践中)的挖掘与沉淀。只有当这些项目知识被有效捕获并变得可访问时,“项目规则”和“Agent Memories”等上下文注入机制才能发挥最大效用,为 AI 提供准确的上下文信息。
模式:自文档化代码增强语义化表达
:传统的软件文档(如规格说明书、设计文档)往往和代码不同步,维护成本高,信息滞后。代码作为开发者沟通的核心媒介,如果本身不够清晰,其他人或 AI 工具就很难理解其功能、意图和交互方式,增加了认知负担和维护难度。
实践方式:语义化命名与结构化设计
:让代码本身成为它最准确、最实时的“文档”。通过系统地应用一系列编码实践(清晰命名、良好结构、明确类型、策略性注释等),让代码的结构、命名和表达方式尽可能清晰直观,让阅读者能直接通过阅读代码来理解,减少对外部文档的依赖。
:
:使用描述性强的变量、函数和类名,比如用 calculateInvoiceTotal() 而不是 calc()。
:遵循设计模式和编码规范,确保代码模块化、可重用。
实践方式:适应于 AI 理解的编程范式
:传统编程往往隐含很多假设,比如函数输入参数的有效范围、返回值关系等没有明确定义。调用者或开发者很难预知这些假设,一旦违背前提就可能引发错误。AI 辅助生成的代码也无法知道这些潜在契约,可能产生行为不可预测的实现。
:在代码中显式定义前置条件、后置条件和不变量。比如在函数签名或注释中声明参数要求,在关键位置加入 assert 断言或使用契约库。这样函数的期望行为就一目了然了,任何调用都必须满足前置条件,保证后置结果符合预期。明确的契约既约束了代码正确性,也为 AI 提供了丰富的语义线索。
:
:在静态强类型语言中,明确的类型注解提供了关于数据结构、函数签名和预期数据流的形式化信息,AI 可以利用这些信息生成类型安全且符合接口的代码。
:通过嵌入形式化的前置条件、后置条件和不变量,精确描述组件的责任和期望行为。
:
:无副作用,输入相同则输出相同,简化 AI 的推理过程。
:避免状态修改,使代码状态更易预测和跟踪。
:模块化和声明式风格可能更易于 AI 理解结构和意图。
当然,语义丰富性和实用性之间需要权衡。像高级类型系统和契约式设计这些技术,为 AI 提供了丰富的语义信息,但采用成本和复杂性也更高。不过,AI 的理解能力确实会随着更明确的语义信息而提高。
模式:验证优先开发
是一种针对 AI 生成代码固有不确定性与幻觉现象而设计的软件开发模式。它以“生成-审查-测试-优化”为核心循环,强调先快速生成,再严格验证与优化,通过多轮迭代保障最终软件产出的正确性、可靠性与可维护性。
在代码生成语境下,
指的是 AI 生成了看似合理但实际上是错误的、不存在的、无意义的或者与事实不符的代码、API 调用、库引用、配置项或逻辑片段。比如,AI 可能自信地使用一个虚构的库函数,或者实现一个听起来合理但算法逻辑完全错误的排序方法。这种现象源于大语言模型基于模式匹配和概率预测的本质,而非真正的理解或推理。
:传统的开发方法论(如 TDD、BDD)强调精确的预先定义和确定性实现,很难适应 AI 代码生成固有的概率性及其可能产生的“幻觉”。直接将未经充分验证的 AI 生成代码集成到系统中,存在质量、安全性和合规性风险,容易快速积累技术债务。
实践方式:生成-审查-测试-优化循环
:采用以“验证”为核心的快速迭代循环,充分利用 AI 的生成效率,同时确保产出符合质量标准。核心原则是承认 AI 输出的概率性,将开发焦点从“规范驱动实现”转向“生成后验证”,并通过严格、多维度的检验管理潜在风险。
:
:使用 AI 工具根据提示快速产出代码、测试用例、文档等。
:结合人工审查与自动化工具(静态分析、风格检查、复杂度分析、安全扫描)评估生成产物。
:采用全面、自动化的验证策略,涵盖功能正确性、性能效率、安全漏洞扫描、鲁棒性及语义正确性。
:根据审查与测试结果,通过人工修改或优化提示词进行改进,形成持续闭环迭代。
检查策略
由于幻觉检测是一个活跃的研究领域,目前的最佳实践通常是结合多种手段:
:将 AI 生成的代码与官方文档、可靠代码库或 API 规范进行比对验证。
:核查生成内容是否与项目现有代码约定、架构模式、设计风格保持一致。
:利用强类型语言特性和静态分析工具,捕捉明显的幻觉,如调用未定义变量、错误函数等。
:检查生成内容中引用的外部库、模块或 API 是否实际存在,并确认版本兼容性。
:部分 AI 工具提供置信度分数,低置信度可能提示存在幻觉风险,但高置信度也不能完全排除错误。
:当前阶段,经验丰富的开发者通过人工走查仍是识别复杂幻觉和微妙逻辑错误最有效的方式。
:针对怀疑存在幻觉的部分,设计特定测试用例验证其功能正确性与边界条件。
模式:面向 AI 理解的代码重构
代码重构是改进现有代码结构和质量而不改变其外在行为的过程。
:代码太长时,AI 可能会无法理解,或者超出上下文限制,或者生成的代码质量不高。这些问题都可以通过重构来解决。许多重构技术天然地有助于提升代码对 AI 的友好度,因为它们的目标通常是降低复杂性、提高可读性和增强模块化。
:当单个函数或文件过长时,AI 模型容易因 token 限制或困惑于多重责任而无法有效理解全局逻辑。
:缺乏清晰的方法划分与命名,令 AI 难以提取关键业务意图,生成的补全或重写质量不高。
实践方式:应用经典重构技术优化代码结构与可读性
代码重构是改进现有代码结构和质量而不改变其外在行为的过程。它通过消除代码异味、提高模块化和可读性,降低复杂度,从而让 AI 工具更容易进行分析和生成高质量代码。
:系统化地采用诸如提炼函数、提炼类、重命名等经典重构手法,消除代码异味,降低每个模块的认知复杂度,从而使 AI 模型更易聚焦于单一职责和业务逻辑。
:
识别“上帝类”或“神对象”:通过静态分析工具定位文件中责任过多的区域。
提炼函数:将长方法中的逻辑块拆分为独立、具名的小方法,比如把“数据校验”、“业务处理”、“日志记录”分别提炼出来。
提炼类:将相近职责或状态数据抽离到新的类中,减少主类耦合度。
重命名:统一变量、方法、类的命名规范,使其业务意图对 AI 更直观。
移除死代码与临时代码:剔除无用方法和注释,降低上下文噪声。
实现方式:借助 AI 辅助分析进行结构性重构
:
:多层调用链与高耦合使得开发者和 AI 都难以把握整体结构,重构风险高。
:缺乏数据支撑,易遗漏高价值改动点或误伤核心功能。
:引入自动化的调用关系分析与依赖追踪技术,结合 AI 辅助解释或总结,形成数据驱动的重构策略。通过理解某个类、模块或方法的使用情况,量化其改动影响范围、频次和耦合度,科学规划重构顺序与粒度。
:
利用静态分析或 LSP 工具,生成目标类/方法的调用点列表、调用频次及调用层级图。
用 AI 对这些结果进行自然语言摘要,帮助快速理解各调用方的上下文。
基于调用热度和耦合度,制定分阶段重构策略:先从高频、深层调用处入手,后续再优化低频、可替换模块。
:比如 AutoDev 提供的
/usage 命令,可以帮助你分析代码的调用情况。
:使用 /usage:com.example.service.OrderService 来分析指定代码的调用情况,了解它的具体使用场景,为后续重构提供依据。
:基于分析结果,由 AI 评估并决定最适合的重构策略。
:AI 调用其 IDE 能力来执行具体的重构操作,并修复过程中可能出现的错误。
相比于让 AI 直接修改代码,借助 IDEA 的内置重构功能来执行,效果会好得多。
注意事项
进行这些重构时,应该采用小步快跑、持续集成和自动化测试的方式,确保不破坏现有功能。通过有针对性的重构,可以系统性地改善代码库结构,让它更易于 AI 理解、导航和生成高质量代码。
定义 AI 友好架构
AI 友好架构是一种将成熟的软件架构原则与生成式 AI 的能力相结合并进行调整的软件构建方法。它的核心目标是创建一个既便于人类协作,又能被 AI 高效理解、分析、生成和持续演进的代码资产与系统环境,从而显著提升开发效率。
将上面提到的模式总结一下,有几个关键点:
:AI 友好的代码库首先必须对人类友好,遵循一致的团队规范、清晰的代码结构,具备良好的可读性和可维护性。
:通过 DDD 的通用语言、领域术语表、提示词工程和需求工具集成,为 AI 提供清晰的任务背景。同时,利用项目规则、Agent 记忆和 RAG 技术,注入项目特定的规范、技术栈和历史决策。
:让代码自文档化,通过语义化命名、结构化设计、类型系统、契约式设计或函数式编程,让代码本身就能传达其功能和意图,减少 AI 理解的障碍。
:采用“验证优先开发”模式,建立“生成-审查-测试-优化”的迭代循环,管理 AI 的不确定性和潜在幻觉,确保最终产出的质量和可靠性。
:通过面向 AI 理解的代码重构,应用经典重构技术,并借助 AI 辅助分析,持续改进代码结构,降低复杂度。
基于上面这些模式,AI 友好的架构可以被看作一个多层次的系统:
:强调清晰的领域知识定义、结构良好且语义丰富的代码库,为 AI 理解和操作奠定基础。
:通过精确的需求信息和项目特定知识,为 AI 提供必要的上下文。
:AI 在此层进行代码生成,并通过严格的验证优先开发流程确保产出质量。
:利用验证结果反馈优化提示词、更新知识库、触发代码重构,并通过持续的知识管理确保持续改进。
当然,AI 友好的架构并不是一成不变的。它会随着技术发展、团队需求和项目演变而不断调整和优化。通过这种方式,团队才能在快速发展的 AI 技术环境中,保持高效的开发流程和高质量的代码产出。
总结
AI 编程不是银弹,它需要团队共同努力。通过结合领域知识、项目规范、代码结构和验证流程,团队才能有效地利用 AI 编程助手的能力,提升开发效率和代码质量。