AI 友好架构是一种将成熟的软件架构原则与生成式 AI 的能力相结合并进行调整的软件构建方法。其核心目标是创建一个既便于人类协作,又能被 AI 高效理解、分析、生成和持续演进的代码资产与系统环境,从而显著提升开发效率。
生成式 AI 在你的代码库上工作不好存在诸多原因,其中一个就是:你的代码库不够 AI 友好。而除了 AI 不友好之外,它存在的另外一个问题就是: 对于人类来说也不友好。 工作于代码库的团队没有保持一致的规范与最佳实践,导致了代码库的可读性差、可维护性差、可扩展性差等问题。
软件工程并没有所谓的真正最佳实践,只有适合于团队的实践。在使用 AI 之前,我们有必要对团队现有的软件工程实践进行一个简单的梳理。
举我们最简单的金融场景作为示例,你有一个产品名称:稳享灵动慧利
,那么你的代码应该怎么写:
通常来说,方案 1 和方案 2 肯定不是你的首选,因为你的 Tech Lead/架构师会告诉你:其他人看不懂 —— 并不止是考虑到外包或者新手程序员,还要考虑其他 人查找代码时也会一脸懵逼。方案 3 和方案 4 是最常见的选择,所以只要团队达成一致即可。
当拼音达到了某种默契之后,会达到另外一种泛滥:用户会被翻译为 YongHu,或者作为福建人经常遇到的梗:YongFu。那么,如何去避免这个问题呢?一种 简单的方法是:代码检视。在这种场景下,你使用 Sonarlint 之类的工具,并不能很好地帮助你解决这个问题,所以我们会依靠于代码检视在一定程度上解决 这个问题。
我们预期代码检视的目的是:
通常来说,你很难通过代码检视来发现潜在的 bug:
所以,多数情况下,代码检视的目的并不是为了发现 bug,而是为了让团队达成一致的规范与团队最佳实践。
我们常常在微观细节上追求代码质量,却可能忽视了宏观架构的持续“腐化”,导致可维护性下降,最终陷入“面条式代码”的困境。这往往是因为我们未能 从结构层面思考:新代码应置于何处? 相关逻辑应如何组织? 错误的放置和无序的增长,不仅使代码超出人类的认知极限,也让 AI 难以有效理解和介入。
应对之道在于构建清晰的结构,这主要依赖于两个相辅相成的策略:
分层定义了“社区”(不同职责的大区域),内聚则定义了“家庭”(功能相关的小单元)。
当现有的设计不够时,重构变成了我们改进的手段,以将不同职责的代码,按照逻辑关系,清晰地划分到不同的“层”或者“模块”中去。
通常来说,在我们使用 AI 进行编程时,会分为多种方式:
两种模式的核心差异在于是否带有用户的上下文信息。
在使用 AI 插件的代码补全时,你会明显发现:生成的代码质量优于 ChatGPT 之类的网页聊天式生成。以 AutoDev 示例项目
假设我们希望生成一个新的 API 接口:delete blog 接口,那么我们会在 getBlog
的结束处添加一个注释:
Untitled 工程中的 BlogController 为例。
@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
便是根据我们前面的代码规范而生成的附加代码,在 deleteBlog
这个方法名同样会受到我们前面代码命名规范的影响。
也因此,当你基于别人的代码修改时,会发现 AI 的质量可能不如你预期的那么好。好的代码会让 AI 生成更好的代码,而屎山则更容易让
AI 生成屎山。当两种类型(规范好和差)的代码非常好的混合在一起时,你就只能靠运气了。
如果你预期 AI 生成一大段代码(多个方法)时,当缺乏足够的上下文时,补全式模型的效果会大打折扣,诸如于生成不存在的方法。而现在的补全式模型往往为了效果更好 ,所以会过度拟合,进而导致难以生成大段代码。
在生成式 AI 时代,问题表达的质量直接影响生成代码的质量。我们在使用 AI 进行编程时,通常会面临两种情况:
在 AI 帮你实现用户管理功能时,他需要先能通过当前的代码库理解什么是用户,什么是管理,才能有限地根据你的需求生成代码。否则,AI 会根据自己的理解 随机生成代码,导致质量不高。
因此,首先我们需要定义:在生成式 AI 时代,什么是好的问题?(这个可以交给 AI 来研究,诸如 Google DeepResearch)。
核心要素初步识别
通过初步搜索,我发现了一些构成有效 AI 提示的关键要素,例如清晰的任务指令、充分的背景信息、具体的细节描述、明确的角色设定、 期望的输出格式以及所需的语气和风格。此外,提供范例和设定约束条件也是很重要的技巧。
简单来说就是好的提示词技巧。有效的提示词需要清晰和具体,诸如于:
结合这些提示词工程技术,特别是充分利用现有代码库的上下文和领域知识,可以显著提高 AI 生成代码的准确性、相关性和质量,使其更好地融入项目。
PS:如这里的子标题,我们借助于 ChatGPT 生成了歇后语。
在现今的 AI 辅助编程工具里,通常会带有大量的不同模型,诸如于:补全、FastApply、聊天、推理等。不同模型参数不一,有的模型参数高达 175B,甚至更高,而有的模型则只有 6B。通常来说,参数越大,模型的理解能力越强。诸如于网传的早期的 Copilot 应用中:补全采用的是 ~ 12B 的模型 Codex,而补全采用的是 ~175B 的模型 GPT-3.5。当你的参数量变小以后,比如采用的是 3B 的补全,模型很难理解复杂的需求,又或者是对于中文的理解能力变得非常有限。在这种时候,可能会出现类似于,你 使用简单的中文描述,模型却无法理解的情况。
当你想实施复杂的需求时,你就需要借助于参数大的模型来构建。因此,模型越大,生成质量越好,但是速度越慢;针对不同场景,需要不同生成策略
通过一篇文章来介绍 AI 友好型架构是一种困难的事,我将尽可能使用模式来抽象,以便于你在不同的场景下进行使用。 这些模式的目标是创建一个既便于人类协作,又能被 AI 高效理解、分析、生成和持续演进的代码资产与系统环境。
问题:用户的需求描述往往模糊不清,AI 难以理解其意图,导致生成的代码质量不高。
解决方案:通过应用领域驱动设计(DDD)的原则,将复杂的业务领域知识显式化、结构化,为 AI 提供更丰富、更精确的上下文信息, 从而提升其对需求的理解和生成代码的质量。
实现示例:在 AutoDev 中,我们通过 AI 生成领域术语表,用户在输入内容后,可以选择优化提示词来丰富上下文信息。
举例:用户的需求是:“实现一个用户管理功能”,那么可以结合领域知识,利用 DDD 的通用语言(Ubiquitous Language),将其重新定义为:实现一个“用户账户管理”的功能,包含“用户注册 (Customer Registration)”、“用户激活 (Account Activation)”、“用户资料更新 (Profile Update)”等。即通过领域术语来丰富需求描述,提供更清晰的上下文信息。
详细定义示例:“(角色:资深 Java 工程师)使用 Spring Boot 框架,实现一个‘客户账户管理’限界上下文的核心功能,包括:
(1)处理 POST 请求 /customers/register
用于客户注册 (Customer Registration),接收包含'name', 'email',
'password' 的 JSON,验证 email 格式和密码强度,成功后保存至数据库并发布‘CustomerRegistered’领域事件。(2)处理 POST 请求
/accounts/activate/{token}
用于账户激活 (Account Activation)……” 这种方式提供了更清晰的业务术语、技术约束和具体行为描述。
解决方案:在 IDE 连接在线的需求相关的工具(如 Jira、Confluence、Notion 等),将用户的输入/需求转换为检索条件(如关键词),调用在线的工具 进行检索,获取相关的需求信息,再将用户的输入转换为带完整需求上下文信息的提示词,交给 AI 进行处理。
实现示例:在 Cursor、Copilot、AutoDev 等工具中,可以通过自定义 Agent、MCP 等工具,让用户可以接入自己的需求工具、智能体,以让用户 按照自己的需求进行检索。
举例:用户输入:“添加支付提醒功能”,可以由工具来提示:在 Jira 中找到 2 条相关 Issue:PAY-102、INVOICE-58,是否将它们的描述和验收标准作为上下文?[是/否]? 由用户来进行确认是不否进行关联,并进一步优化提示词。
问题:AI 编程助手通常缺乏对特定项目的深入理解,包括其编码规范、技术栈选型、架构设计、领域术语、历史决策以及团队约定等。当开发者与 AI 交互时, 若不手动提供这些上下文,AI 生成的代码或建议往往是通用的,可能不符合项目标准、引入不兼容的技术、或与现有架构冲突。反复手动提供完整上下文效率低下且易遗漏关键信息。
解决方案:构建一个系统性的框架,使 AI 助手能够自动、持续地获取并利用项目特定的知识和规则,从而将其响应“锚定”在项目的实际环境中。
问题:在每次与 AI 交互时手动提供完整的项目特定上下文(编码规范、技术栈、架构模式、API 约定等)效率低下且容易遗漏,导致 AI 生成的代码不符合项目要求或团队标准。
解决方案:利用现代 AI 编码助手提供的“项目规则”或“自定义指令”功能,预先定义项目上下文信息,使 AI 能够自动加载并遵循这些规则。这需要结合有效的知识捕获和管理策略, 并通过检索增强生成(RAG)技术将知识提供给 AI。
实现示例:利用现代 AI 编码助手提供的“项目规则”、“自定义指令”或类似配置功能。
举例:在项目特定位置(如 .cursorrules
, .github/copilot-instructions.md
, IDE 设置)创建配置文件。其可能包含以下内容:
通过有效利用这些 AI 代码助手内置的规则配置功能,团队可以显著提升 AI 代码助手的实用性和可靠性,使其更好地融入特定项目的开发流程。
解决方案:让 AI Agent 能够利用项目中不断积累和演变的动态知识,以及用户偏好,理解更深层次的背景、决策逻辑和隐性经验。
实现机制:利用 AI Agent 的 "Memories" 功能,将关于工作区和用户偏好的关键信息持久化存储在本地(例如,可能存储在项目下的
.augment
文件夹内的 augment.memories
文件中)。系统会自动学习并更新这些记忆,同时用户也可以通过直接编辑记忆文件、点击交互界面中的
按钮或向 Agent 发出明确指令来手动管理记忆内容。这些存储的记忆随后作为检索增强生成(RAG)的关键知识源:系统在处理请求时会检索相关的记忆,
并将其与当前上下文(如代码库信息)一同注入给大语言模型,从而生成更符合用户需求和项目背景的响应。
举例:诸如 Augment 的 Memories 会记录用户在对话中的关键细节,以 augment.memories
保存在项目中,以供后续对话时使用。
以下是之前记录的一些具体用户偏好示例,这些都可能被存储在 augment-memories.md
中:
潜在的挑战:记忆污染/损坏:自动学习可能出错,手动编辑也可能引入错误;透明度不足:难以完全理解记忆内容及其影响;管理开销: 需要用户主动监控和清理,增加负担;安全风险:本地文件可能成为提示注入的目标。
需要强调的是,“项目知识驱动的上下文注入”模式的成功实施,高度依赖于团队持续进行有效的知识管理。这包括对显式知识 资源(如项目文档、知识库、代码注释、设计规范)的系统性维护,以及对隐式知识 (蕴含在团队经验、历史决策、沟通讨论、开发实践中)的挖掘与沉淀。只有当这些项目知识被有效捕获并变得可访问时,“项目规则”和“Agent Memories”等上下文注入机制才能发挥最大效用,(通常通过 RAG 技术)为 AI 提供准确、丰富的上下文信息。
问题: 传统的软件文档(如外部规格说明书、设计文档、注释文件)往往与代码实现不同步,维护成本高,信息滞后 。代码作为开发者沟通的核心媒介,如果本身不够清晰,会导致其他开发者(或 AI 工具)难以理解其功能、意图和交互方式,增加了认知负荷和维护难度 。
解决方案: 让代码本身成为其最主要、最准确的“文档” 。通过系统性地应用一系列编码实践(如清晰命名、良好结构、明确类型、策略性注释等),使代码的结构、命名和表达方式尽可能清晰、直观,从而让阅读者能够直接通过阅读代码来理解,减少对外部解释的依赖 。
实现示例:
问题:传统编程往往隐含许多假设:函数输入参数的有效范围、返回值关系等没有明确定义。调用者或开发者难以预知这些假设,一旦违背前提就可能引发错误。AI 辅助生成的代码也无法知道这些潜在契约,可能产生行为不可预测的实现。
解决方案:在代码中显式定义前置条件(Precondition)、后置条件(Postcondition)和不变量(Invariant)。例如,在函数签名或注释中声明参数要求, 在关键位置加入断言(assert)或使用契约库。这样函数的期望行为清晰可见,任何调用都必须满足前置条件,保证后置结果符合预期。明确的契约既约束了代码正确性,也为 AI 提供了语义线索。
实现示例:
requires
)、后置条件 (ensures
) 和不变量 (
invariant
),精确描述组件的责任和期望行为。语义丰富性与实用性的权衡:像高级类型系统和 DbC 这样的技术为 AI 提供了显著更丰富的语义信息,但与基本的命名约定或注释相比,它们的采用成本和复杂性更高。AI
的理解能力随着更明确的语义信息的增加而提高。
验证优先开发(Validation-First Development,简称 VFD)是一种针对 AI 生成代码固有不确定性与幻觉现象而设计的软件开发模式。它以“生成-审查-测试-优化”为核心循环,强调 先快速生成,再严格验证与优化,通过多轮迭代保障最终软件产出的正确性、可靠性与可维护性。
在代码生成语境下,幻觉指的是 AI 生成了看似合理但实际上是错误的、不存在的、无意义的或者与事实不符的代码、API 调用、库引用、配置项或逻辑片段。例如,AI 可能自信地使用一个虚构的库函数,或者实现一个听起来合理但算法逻辑完全错误的排序方法。 这种现象源于大型语言模型基于模式匹配和概率预测的本质,而非真正的理解或推理。
背景问题:传统开发方法论(如 TDD、BDD)强调精确的预先定义和确定性实现,难以适应 AI
代码生成固有的概率性及其可能产生的“幻觉”(即看似合理但实际上错误的代码)。
直接将未经充分验证的 AI 生成代码集成到系统中,存在质量、安全性和合规性风险,容易快速积累技术债务。
解决方案:采用以“验证”为核心的快速迭代循环,充分利用 AI 的生成效率,同时确保产出符合质量标准。
核心原则是承认 AI 输出的概率性,将开发焦点从“规范驱动实现”转向“生成后验证”,并通过严格、多维度的检验管理潜在风险。
实现示例:
由于幻觉检测是一个活跃且未完全解决的研究领域,目前最佳实践通常是结合多种手段进行:
代码重构是改进现有代码结构和质量而不改变其外在行为的过程。
问题:代码太长时,AI 可能会无法理解,或者超出上下文,或者是生成的代码质量不高,可以通过重构来解决这个问题。许多重构技术天然地有助于提升代码对 AI 的友好度,因为它们的目标通常是降低复杂性、提高可读性和增强模块化。
定义:代码重构是改进现有代码结构和质量而不改变其外在行为的过程,通过消除代码异味、提高模块化和可读性,降低复杂度,从而更易于 AI 工具进行分析和生成高质量代码
解决方案:系统化采用诸如提炼函数(Extract Method)、提炼类(Extract Class)、重命名(Rename Variable/Method)、等经典重构手法,消除代码异味, 降低每个模块的认知复杂度,从而使 AI 模型更易聚焦于单一职责和业务逻辑
实现示例:
AI 直接重构的问题:
解决方案:引入自动化的调用关系分析(Usage Analysis)与依赖追踪(Dependency Tracking)技术,结合 AI 辅助解释或总结, 形成数据驱动的重构策略。通过理解某个类、模块、方法的使用情况,量化其改动影响范围、频次、耦合度,科学规划重构顺序与粒度。
解决方案:
实现示例:诸如 AutoDev 提供的 /usage
命令,可以帮助你分析代码的调用情况。
/usage
命令(例如 /usage:com.example.service.OrderService
)来分析指定代码(如
OrderService
)的调用情况。目的是通过了解代码的具体使用场景(例如:“OrderService.processOrder 在 CheckoutController 中被调用
8 次用于提交订单;在 BatchProcessor 中被异步调度 3 次用于批量结算。”)为后续重构提供依据。与 AI 纯修改相比,借助于 Intellij IDEA 的重构功能才能更好地进行重构。
进行这些重构时,应采用小步快跑、持续集成和自动化测试(如遵循 Red-Green-Refactor 模式)来确保不破坏现有功能。通过有针对性的重构,可以系统性地改善代码库结构,使其更易于 AI 理解、导航和生成高质量代码。
AI 友好架构是一种将成熟的软件架构原则与生成式 AI 的能力相结合并进行调整的软件构建方法。其核心目标是创建一个既便于人类协作,又能被 AI 高效理解、分析、生成和持续演进的代码资产与系统环境,从而显著提升开发效率。
将上述的模式总结为表格:
模式名称 | 主要目标 | 关键技术/示例 | AI 交互阶段 | 主要益处 | 关键挑战/依赖 |
---|---|---|---|---|---|
1. 领域知识丰富上下文与问题定义 | 提升 AI 对用户意图和需求的理解 | DDD 通用语言, 提示词工程, 术语表生成, 需求工具集成 (Jira) | 输入处理 | 减少歧义, 提高生成代码的业务准确性 | 需要建立和维护领域知识体系, 需求管理流程集成 |
2. 基于项目知识与规范的智能生成 | 使 AI 生成的代码符合特定项目规范和上下文 | 项目规则 (Project Rules), Agent Memories, RAG, 自定义指令 | 生成 | 生成的代码更贴合项目实际, 减少返工 | 需要持续捕获、维护和更新项目知识, RAG 效果依赖知识质量 |
3. 自文档化代码增强语义表达 | 让代码本身传递更多意图和约束信息 | 语义化命名, 结构化设计, 类型系统, 契约式设计 (DbC), 函数式编程 (FP) | 代码本身/分析 | 减少对外部文档依赖, AI 更易理解代码逻辑和约束 | 高级范式 (DbC, FP) 学习曲线陡峭, 实施成本较高 |
4. 验证优先开发 (VFD) | 管理 AI 生成代码的不确定性, 确保最终质量 | 生成-审查-测试-优化循环, 幻觉检测策略, 全面自动化测试 (属性基测试, 模糊测试等) | 输出验证/优化 | 安全地利用 AI 生成能力, 控制质量风险, 持续改进 | 需要建立强大的验证体系, 人工审查仍不可或缺 |
5. 面向 AI 理解的代码重构 | 降低现有代码复杂度, 提升 AI 处理效率和理解能力 | 提炼函数/类, 重命名, 移除死代码, AI 辅助调用分析 (/usage) | 代码处理/分析 | AI 更易分析/修改代码, 提高 AI 辅助工具效果 | 需要投入重构成本, 依赖自动化测试保障安全 |
上述模式的几个关键点是:
而基于以上模式,AI 友好的架构可以被视为一个多层次的系统,涵盖了从基础知识到交互情境化、生成与验证、以及持续改进反馈的各个方面。
当然,AI 友好的架构并不是一成不变的,它需要随着技术的发展、团队的需求和项目的演变而不断调整和优化。通过这种方式,团队可以在快速发展的 AI 技术环境中,保持高效的开发流程和高质量的代码产出。
AI 编程并不是银弹,而是一个需要团队共同努力的过程。通过结合领域知识、项目规范、代码结构和验证流程,团队可以有效地利用 AI 编程助手的能力,提升开发效率和代码质量。
围观我的Github Idea墙, 也许,你会遇到心仪的项目