尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

AI辅助编程失控?从代码沼泽到工程化重构的实战复盘

AI辅助编程失控?从代码沼泽到工程化重构的实战复盘 这两年AI 辅助编程工具越来越强项目的起步速度确实快了不少。但我也踩了一个非常典型的坑在 AI 的“帮助”下我把项目越写越乱最后代码膨胀、依赖失控、逻辑互相矛盾几乎到了无法维护的地步。这篇文章想复盘一下我是怎么把自己写进墙角的以及最后如何一步步把项目从失控状态救回来。文章会围绕实际项目经历展开讨论 AI 编程中常见的失控场景、问题根因、重构思路以及现在我在 AI 辅助开发中坚持的工程规范。全程会给出关键代码示意、排查方法和阶段拆分清单希望能给同样依赖 AI 编码、又担心代码质量的开发者一些参考。1. 背景AI 辅助编码怎么把项目“写进墙角”先说明一下背景。我最近在做一个面向企业客户的内部资产管理平台技术栈选的是 Spring Boot Vue 3 MySQL整个项目的初始版本基本是靠 AI 编程工具完成的。最开始效率确实很高几周时间就搭出了原型接口、页面、数据表都齐了。但问题也随之而来AI 生成的代码非常“多”但也非常“散”。同一个业务逻辑在多个接口里被重复实现。工具类、DTO、VO 越来越多职责却越来越模糊。依赖包被反复添加谁在用、用在哪完全说不清。AI 根据局部需求不断“打补丁”导致整体架构没有统一约束。有一段时间每次加新功能我都需要先花大量时间寻找旧代码怕改坏已有逻辑。更可怕的是好几次 AI 推荐的重构方式反而把原本稳定的模块搞坏了。这种状态用一句话总结就是我依赖 AI 把代码写出来了但没有用工程标准去约束 AI结果项目失去了可维护性。1.1 什么是“用 AI 把自己写进墙角”这里说的“写进墙角”不是指代码编译不过也不是指某个 bug 修不好而是指一种系统性的技术债务代码规模失控随着 AI 不断增量生成代码行数快速增长但结构没有同步优化。依赖失控任何问题都用“加依赖”解决导致 pom.xml 越来越臃肿。抽象失控AI 生成的类、接口、枚举名大量冗余层次关系混乱。可测试性失控代码耦合度高单元测试难以编写。可追溯性失控已经说不清某段业务逻辑是谁写的、为什么这么写。当这些问题叠加在一起项目就会进入“改东墙补西墙”的恶性循环。这跟传统开发中讨论的技术债务本质是一样的但 AI 加快了债务的累积速度因为生成速度太快而审查速度跟不上。1.2 为什么 AI 编程会加速代码失控AI 编程工具的本质是“根据当前上下文预测和生成代码”。它没有完整的项目全局观也不理解产品的长期演进方向。因此AI 倾向于满足当前提示词而不是当前架构。AI 会重复使用它见过的常见模式而不是项目内统一的定制模式。AI 对过时代码、废弃代码不会主动清理。AI 不会主动为“未来的扩展”预留合理设计除非你在提示词里明确约束。所以AI 可以是一个非常高效的“编码执行者”但无法替代“架构师”和“代码审查者”的角色。一旦开发者把架构决策权也交给 AI项目失控几乎是必然的。2. 失控现场项目进入“代码沼泽”阶段下面直接复盘那个资产管理系统是怎么一步步失控的。这个阶段我们经历了大概三周时间功能表面上越来越多但内部质量肉眼可见地恶化。2.1 依赖管理失控最开始项目只有 Spring Web、MyBatis-Plus、MySQL 驱动、Lombok 这些基础依赖。但随着 AI 生成各种功能模块依赖越来越多dependencies !-- 基础框架 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- ORM 框架 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- 数据库驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 导入导出 Excel -- dependency groupIdcn.afterturn/groupId artifactIdeasypoi-spring-boot-starter/artifactId version4.4.0/version /dependency !-- 定时任务 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-quartz/artifactId /dependency !-- 又新增了 Excel 解析库因为 easypoi 某些 API 不好用 -- dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency !-- 又新增了 hutool 工具包因为有些日期处理懒得自己写 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.18/version /dependency /dependencies这只是其中一部分。实际项目中同一个功能存在两套实现方案的情况非常多。例如 Excel 导入导出既有 easypoi 又有 easyexcel两套工具类、两套模板文件维护成本直接翻倍。2.2 重复代码泛滥AI 生成的代码很少考虑“复用已有的工具类”更多时候是根据提示词直接生成一段“看起来能跑”的新代码。举一个比较典型的例子原来的资产编号工具类// 文件路径src/main/java/com/company/asset/common/util/AssetCodeGenerator.java package com.company.asset.common.util; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.atomic.AtomicInteger; /** * 资产编号生成器 * 规则ZC yyyyMMdd 四位流水号 */ public class AssetCodeGenerator { private static final DateTimeFormatter DATE_FORMAT DateTimeFormatter.ofPattern(yyyyMMdd); private static final AtomicInteger SEQUENCE new AtomicInteger(1); private AssetCodeGenerator() { } public static String generate() { return ZC LocalDateTime.now().format(DATE_FORMAT) String.format(%04d, SEQUENCE.getAndIncrement()); } }后来 AI 在别的接口里“重新发明”了一段类似的逻辑// 文件路径src/main/java/com/company/asset/module/repair/util/RepairNoUtils.java package com.company.asset.module.repair.util; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Random; /** * 维修单号生成工具 */ public class RepairNoUtils { private RepairNoUtils() { } public static String createRepairNo() { // 注意这里没有使用统一规则而是重新实现了一遍 String date LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMMdd)); int randomNum new Random().nextInt(9999); return WX date String.format(%04d, randomNum); } }这两个类都做编号生成但规则完全不同。后来客户要求所有单据号统一格式我就需要同时改多个类而且还要排查哪些地方用了随机数、哪些地方用了流水号。类似这样的重复代码遍布整个项目A 模块写了一套分页返回结构B 模块又自己写了一套C 模块写了一个 UserContext 获取当前登录用户D 模块又复制了一份只是改了个包名。2.3 分层混乱与循环依赖AI 生成代码时经常无视项目的分层原则。它会把 Controller 里的业务逻辑写得非常重把 Service 写成一个纯粹的“传声筒”甚至把 SQL 拼接直接放在 Controller 里。更严重的是随着功能不断叠加类与类之间出现了循环依赖AssetController → AssetService → AssetAuditService → AssetServiceSpring 启动时会直接报错*************************** APPLICATION FAILED TO START *************************** Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | assetServiceImpl (field private com.company.asset.service.AssetAuditService com.company.asset.service.impl.AssetServiceImpl.auditService) ↑ ↓ | assetAuditServiceImpl (field private com.company.asset.service.AssetService com.company.asset.service.impl.AssetAuditServiceImpl.assetService) └─────┘出现这种问题之后AI 给出的建议往往是加Lazy注解来“绕过”启动检查。这是典型的治标不治本Service public class AssetServiceImpl implements AssetService { // 用 Lazy 绕过了循环依赖 Lazy Autowired private AssetAuditService auditService; // 业务代码... }加了Lazy项目能启动了但循环依赖的本质问题还在两个服务职责边界不清晰后续任何修改都可能引发连锁问题。2.4 数据库表结构混乱AI 不仅生成代码还会帮你生成数据库表。当提示词里描述比较模糊时AI 会“自行发挥”设计表结构。几次迭代后库里的表出现了大量冗余字段和无效索引。例如资产表asset_info被反复多次修改最终字段数量达到 47 个其中不少字段含义重叠-- 资产表部分字段 CREATE TABLE asset_info ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, asset_code VARCHAR(64) NOT NULL COMMENT 资产编号, asset_name VARCHAR(128) NOT NULL COMMENT 资产名称, category VARCHAR(64) DEFAULT NULL COMMENT 资产分类, category_name VARCHAR(64) DEFAULT NULL COMMENT 分类名称与 category 重复, status TINYINT DEFAULT 0 COMMENT 状态0-在库1-借用2-维修3-报废, status_desc VARCHAR(32) DEFAULT NULL COMMENT 状态描述冗余字段, owner_user_id BIGINT DEFAULT NULL COMMENT 使用人ID, owner_user_name VARCHAR(64) DEFAULT NULL COMMENT 使用人姓名反范式设计容易不一致, purchase_price DECIMAL(10,2) DEFAULT NULL COMMENT 采购价格, price DECIMAL(10,2) DEFAULT NULL COMMENT 资产价格与 purchase_price 重复, purchase_date DATETIME DEFAULT NULL COMMENT 采购日期, buy_time DATETIME DEFAULT NULL COMMENT 购买时间与 purchase_date 重复, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_category (category), KEY idx_owner (owner_user_id), KEY idx_status (status) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4 COMMENT 资产信息表;字段冗余带来的问题不仅仅是存储浪费更重要的是业务代码不知道应该读写哪个字段。有的接口写status_desc有的接口读status自己去翻译最后两边数据不一致。当时我甚至做过一次统计整个数据库里类似status与status_desc、purchase_price与price这样语义重复的字段至少有 10 组。3. 问题复盘是什么让 AI 辅助开发走向失控走出墙角之前必须先想清楚为什么会走进墙角。当时我复盘下来核心原因可以归纳成以下四点。3.1 把 AI 当成“需求理解者”而不是“代码生成器”我通常直接在提示词里描述完整的业务场景例如帮我写一个资产报废接口报废需要校验资产状态必须是“在库”报废后需要更新资产状态、记录报废时间、发送通知给财务人员。AI 会照做但它理解的“报废”只是它从训练数据里归纳出来的通用报废逻辑不一定是你们公司的报废逻辑。比如你们公司规定“不满一年的资产报废需要部门总监审批”这种业务规则如果不写进提示词AI 就不会知道。这不是 AI 不行而是我错误地把 AI 当成了“懂业务的同事”。实际上AI 只是一位“编码助手”需求理解仍然应该由人来完成。3.2 缺少架构约束和代码评审传统开发中代码评审是保证质量的关键环节。但在 AI 辅助开发时因为生成速度快我经常跳过评审直接提交甚至直接让 AI 生成 commit message。等代码量大到一定程度再想补评审就非常困难了。因为一眼望去到处都是不熟悉的代码根本不知道从哪看起。3.3 提示词不统一上下文碎片化我让 AI 写接口时并没有给它提供项目现有代码结构、命名规范、分层方式、统一返回结果类等信息。每次都是“从零开始”描述AI 每次也“从零开始”生成。这就导致同一个项目的代码风格可能横跨多种模式有的类用 Lombok、有的类手写 getter/setter、有的接口返回RT、有的直接返回 Map。3.4 陷入了“加法陷阱”AI 辅助开发时我总是倾向于“加”而不是“改”加新字段而不是修改旧字段加新工具类而不是复用旧工具类加新接口而不是调整已有接口加新配置而不是清理废弃配置。原因是“加”对 AI 来说更容易——它不需要理解旧代码只需要根据新的提示词生成新内容。但这样一来项目的复杂度只增不减很快就膨胀到难以维护。4. 走出墙角重构与治理实操项目进入“代码沼泽”状态后我停掉了所有新功能开发用两周时间做了一次系统的“代码治理”。这个过程可以分为五个阶段。4.1 阶段一梳理全局建立代码地图首先我使用 IDE 的依赖结构分析功能和代码搜索工具把项目的整体结构梳理清楚。重点回答以下问题整个项目包含多少个模块、多少个 Controller、多少个 Service有没有循环依赖哪些类存在重复职责哪些依赖是实际被使用的哪些表存在冗余字段我给团队做了一个简单的代码统计表| 代码资产 | 数量 | 问题 | | ---------------------- | ----- | ----------------------------- | | Java 文件 | 387 | 部分类职责不清 | | Controller 类 | 45 | 部分 Controller 包含业务逻辑 | | Service 接口/实现 | 80 | 存在循环依赖 | | DTO/VO/BO 类 | 120 | 大量重复结构 | | Mapper 接口 | 35 | SQL 分散部分复杂查询无注释 | | 数据库表 | 28 | 存在重复字段和无效索引 |有了这份“账单”我才能确定重构的优先级。4.2 阶段二整理依赖消除重复方案依赖治理是第一优先级因为依赖混乱会影响整个项目的构建和运行。具体做法# 1. 使用 Maven 依赖分析插件检查依赖树 mvn dependency:tree # 2. 查找未使用的依赖 mvn dependency:analyzedependency:analyze会输出类似下面的结果[WARNING] Used undeclared dependencies found: org.springframework.boot:spring-boot-starter-validation [WARNING] Unused declared dependencies found: cn.afterturn:easypoi-spring-boot-starter com.alibaba:easyexcel然后我根据分析结果做了两件事统一 Excel 处理方案由于 easypoi 和 easyexcel 功能重叠我选择保留更符合项目需求的 easyexcel并迁移相关代码删除 easypoi。删除不必要的工具依赖hutool 虽然方便但项目里只用到了日期处理和字符串处理而这些 Spring 和 JDK 本身就支持。为了避免“为了用工具而用工具”我把相关代码改为 JDK 原生实现后移除了 hutool。最终的 pom.xml 去掉了大约三分之一冗余依赖。4.3 阶段三拆分循环依赖明确分层职责循环依赖表明两个 Service 之间边界不清晰。我花了一个下午梳理业务调用关系将AssetService和AssetAuditService的职责重新划分AssetService只负责资产的基本增删改查和状态流转。AssetAuditService负责资产的审批流程通过事件或单独调用的方式触发资产状态变更而不是反过来AssetService再依赖AssetAuditService。具体做法是抽取一个审批事件发布器把两者的依赖关系解耦。先定义事件对象// 文件路径src/main/java/com/company/asset/module/asset/event/AssetAuditEvent.java package com.company.asset.module.asset.event; import lombok.Getter; import org.springframework.context.ApplicationEvent; /** * 资产审批完成事件 */ Getter public class AssetAuditEvent extends ApplicationEvent { private final Long assetId; private final String auditResult; private final String auditor; public AssetAuditEvent(Object source, Long assetId, String auditResult, String auditor) { super(source); this.assetId assetId; this.auditResult auditResult; this.auditor auditor; } }然后AssetAuditService在审批通过后发布事件// 文件路径src/main/java/com/company/asset/module/asset/service/impl/AssetAuditServiceImpl.java package com.company.asset.module.asset.service.impl; import com.company.asset.module.asset.event.AssetAuditEvent; import com.company.asset.module.asset.service.AssetAuditService; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; Service RequiredArgsConstructor public class AssetAuditServiceImpl implements AssetAuditService { private final ApplicationEventPublisher eventPublisher; Override public void approve(Long assetId, String auditor) { // 省略审批核心逻辑 // 审批完成后发布事件 eventPublisher.publishEvent(new AssetAuditEvent(this, assetId, APPROVED, auditor)); } }事件监听方再处理资产状态变更// 文件路径src/main/java/com/company/asset/module/asset/event/AssetAuditEventListener.java package com.company.asset.module.asset.event; import com.company.asset.module.asset.service.AssetService; import lombok.RequiredArgsConstructor; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; Component RequiredArgsConstructor public class AssetAuditEventListener { private final AssetService assetService; EventListener public void onAuditCompleted(AssetAuditEvent event) { if (APPROVED.equals(event.getAuditResult())) { assetService.changeStatus(event.getAssetId(), IN_USE); } } }这样AssetService不再依赖AssetAuditService循环依赖被彻底断开。4.4 阶段四重构公共代码统一重复逻辑循环依赖解决后我开始清理重复代码。重点是把散落的编号生成、分页返回、用户上下文、Excel 导入导出等逻辑统一成公共组件。以编号生成为例我重新设计了一个通用的编号生成器// 文件路径src/main/java/com/company/asset/common/util/BizCodeGenerator.java package com.company.asset.common.util; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * 业务编号统一生成器 * 所有单据编号统一走这里避免各模块自行实现导致规则不一致。 * * 规则示例 * - 资产编号ZC yyyyMMddHHmmss 四位流水 * - 维修单号WX yyyyMMddHHmmss 四位流水 * - 审批单号SP yyyyMMddHHmmss 四位流水 */ public class BizCodeGenerator { private static final DateTimeFormatter DATE_FORMAT DateTimeFormatter.ofPattern(yyyyMMddHHmmss); private static final MapString, AtomicInteger SEQUENCE_MAP new ConcurrentHashMap(); private BizCodeGenerator() { } public static String generate(String prefix) { AtomicInteger sequence SEQUENCE_MAP.computeIfAbsent(prefix, k - new AtomicInteger(1)); return prefix LocalDateTime.now().format(DATE_FORMAT) String.format(%04d, sequence.getAndIncrement()); } }然后删除AssetCodeGenerator、RepairNoUtils等重复类统一使用新工具类String assetCode BizCodeGenerator.generate(ZC); String repairNo BizCodeGenerator.generate(WX); String auditNo BizCodeGenerator.generate(SP);这里需要说明的一点是如果项目部署在多实例环境使用AtomicInteger内存计数仍然会出现重复编号生产环境应该使用数据库序列、Redis 自增等方式。这个工具类的定位是单体应用或开发环境的统一规则示例线上部署时需要替换为分布式 ID 方案。4.5 阶段五数据库瘦身与回归验证代码重构完成后我开始处理数据库表结构问题。先把明显冗余的字段标记出来和业务方确认后执行删除。例如-- 1. 移除冗余字段category_name ALTER TABLE asset_info DROP COLUMN category_name; -- 2. 移除冗余字段status_desc状态描述由代码枚举统一处理 ALTER TABLE asset_info DROP COLUMN status_desc; -- 3. 合并重复金额字段保留 purchase_price删除 price ALTER TABLE asset_info DROP COLUMN price; -- 4. 合并重复时间字段保留 purchase_date删除 buy_time ALTER TABLE asset_info DROP COLUMN buy_time;注意以上 SQL 只作为“瘦身示意”。生产环境执行 DDL 前必须经过完整的备份、评审、灰度流程。如果表数据量很大还需要评估锁表时间和在线变更方案。数据库结构调整后我基于所有核心接口做了一轮完整回归测试重点验证资产新增时编号是否唯一资产状态流转是否符合预期审批通过后是否能正确触发状态变更导出 Excel 功能是否正常。回归测试通过后整个项目的“瘦身”任务才算真正收尾。5. 如何重新定义“人的工作”从编码者回归架构师经过这次重构我最大的感悟是AI 辅助开发中人的角色必须从“编码者”变成“架构师 审查者 需求翻译者”。5.1 把需求拆解成 AI 能理解的最小任务以前我会直接让 AI 写一个完整的“报废流程”。现在我会把流程拆成多个小步骤逐步让 AI 实现校验当前资产状态更新资产状态记录操作日志发送通知处理审批逻辑。每一步都是独立的、可验证的、可回滚的。这样即使 AI 生成的某一步有问题影响范围也是可控的。5.2 在提示词中注入项目规范每次让 AI 生成代码时我会先提供项目规范片段。例如项目使用 Spring Boot 3 MyBatis-Plus。 统一返回结果类为 RT定义在 common/result/R.java。 所有 Controller 层不写业务逻辑只做参数接收和返回。 Service 层必须面向接口编程。 DTO 统一使用 Lombok Data。 异常统一抛出 BizException由全局异常处理器捕获。 数据库操作必须使用 MyBatis-Plus 提供的 LambdaQueryWrapper不允许手写繁琐的 QueryWrapper。这些上下文帮助 AI 生成更贴近项目现状的代码而不是凭训练数据“自由发挥”。5.3 所有 AI 生成代码必须经过“人工评审”这一条不能省。现在我的工作流固定为AI 生成代码我阅读代码检查逻辑正确性我检查是否符合项目规范我补充单元测试或关键场景测试确认无误后再合入分支。即使这样会降低一些“编写速度”但长远来看它避免了后期更大的返工成本。5.4 用“契约测试”约束 AI 的修改当项目规模变大后AI 修改一个方法可能会影响其他调用方。为了解决这个问题我为核心方法补充了单元测试和契约测试确保 AI 的修改不会破坏已有行为。一个简单的 Service 测试示例// 文件路径src/test/java/com/company/asset/module/asset/service/AssetServiceTest.java package com.company.asset.module.asset.service; import com.company.asset.module.asset.dto.AssetCreateRequest; import com.company.asset.module.asset.service.impl.AssetServiceImpl; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.assertNotNull; SpringBootTest class AssetServiceTest { Autowired private AssetService assetService; Test void createAsset_shouldGenerateCode() { AssetCreateRequest request new AssetCreateRequest(); request.setAssetName(测试笔记本电脑); request.setCategory(ELECTRONICS); request.setPurchasePrice(new BigDecimal(4999.00)); Long assetId assetService.createAsset(request); assertNotNull(assetId); String code assetService.getAssetById(assetId).getAssetCode(); assertNotNull(code); } }有了这些测试作为“行为契约”AI 在重构代码时如果有行为不一致测试会直接暴露问题不需要靠人工肉眼比对。6. 常见问题与排查思路在 AI 辅助开发和代码治理过程中我整理了几个高频问题方便大家对照排查。问题现象常见原因解决思路Spring 启动报循环依赖Service 互相注入抽取事件或中间层切断循环依赖而不是加Lazy绕过项目依赖增多但不知道谁在用AI 每次根据提示词“加依赖”使用mvn dependency:analyze检查无用依赖统一功能方案多个工具类功能重复AI 在不同模块分别生成建立 common 模块统一工具类入口删除重复实现数据库字段语义重叠AI 根据不同的提示词反复加字段先梳理数据字典再执行 DDL 瘦身必须经过备份和评审接口返回结构不统一AI 生成代码时未提供统一返回类信息在提示词中明确RT规范人工评审兜底同一个 Service 越来越臃肿所有业务逻辑都堆进一个类按照业务域拆分 Service明确每个类的职责边界代码风格五花八门每次生成时上下文不一致沉淀项目编码规范在提示词中固定传入关键规范改动一个方法其他接口出错缺少单元测试和契约测试为核心方法补充测试让 AI 修改后能快速回归如果你正好也卡在这些问题上建议按下面顺序处理先停掉新功能开发明确技术债务范围。使用依赖分析工具 代码搜索建立问题清单。优先修复循环依赖和重复依赖这两项对项目影响最大。再处理重复代码和数据库冗余字段。每次重构后都执行完整的回归测试。7. 最佳实践构建持续健康的 AI 辅助开发流程经历过这次“写进墙角”之后我总结出了一套更稳健的 AI 辅助开发流程。它不能保证代码完美但能极大降低失控概率。7.1 建立“项目上下文包”我把项目规范整理成一个独立文档每次让 AI 写代码时直接复制进去。这个文档包含项目技术栈和版本分层规范Controller 做什么、Service 做什么、Mapper 做什么统一返回结构统一异常处理方式命名规范类名、方法名、变量名数据库操作规范统一的工具类列表。这样做虽然会让提示词变长但 AI 生成的代码质量和一致性会显著提升。7.2 坚持小步提交 可回滚不要让 AI 一次性生成大量代码而是采取“小步快跑”策略每次只让 AI 实现一个完整的小功能代码生成后立即编译、测试确认无误后再让 AI 做下一个功能如果发现方向错了立刻回退不要继续叠加补丁。7.3 为 AI 生成代码设置边界有些代码不适合让 AI 直接写例如涉及金额计算的核心逻辑复杂的权限控制需要强一致性的数据操作核心报表统计。这些场景人必须亲自编写或至少逐行审查因为 AI 很难在局部上下文中理解复杂的业务约束和合规要求。7.4 定期做“代码瘦身”即使没有明显的失控迹象也应该每隔一段时间做一次代码治理检查无效依赖搜索重复代码检查循环依赖清理注释掉的代码删除无用的 DTO/VO清理数据库无效字段。这个过程不需要停掉所有开发可以安排在迭代间隙进行。但如果不做技术债就会像滚雪球一样越滚越大。7.5 注重可测试性AI 生成的代码通常“能跑”但“很难测”。为了让项目长期可维护我要求 AI 生成代码时遵循依赖注入原则避免静态方法满天飞、避免在构造函数里做复杂初始化、避免把外部 API 调用写死在业务代码里。改造后的代码往往更容易编写单元测试而这些测试又会反过来约束 AI 后续修改形成一个良性循环。8. 总结与下一步建议这次“AI 编码陷入困境再到重构”的经历让我对 AI 辅助开发有了更深的理解。AI 编程工具确实能大幅提升编码速度但它不能替代工程化思维。项目的可维护性依然需要人来保证。依赖管理、分层边界、代码复用、测试覆盖、数据库设计这些基本功在 AI 时代不仅没有过时反而变得更加重要。如果你也正在使用 AI 编程工具可以从今天开始做几件事整理一份项目规范文档让 AI 在规范范围内生成代码。检查现有项目的依赖树清理无效依赖。搜索重复工具类把相似逻辑收敛成一个统一入口。为最核心的业务方法补上单元测试。每次 AI 生成代码后像审查同事的代码一样审查它。技术上下一步可以继续学习这些方向使用更结构化的提示词工程来管理 AI 生成行为建立更完善的 CI 流水线让每次 AI 提交都能自动触发编译和测试深入研究领域驱动设计DDD从更高的视角组织代码边界关注 AI 编程工具的新能力例如项目级上下文、代码库索引、多文件修改等。但无论工具怎么进化有一点不会变代码质量最终由人负责。AI 可以帮你写代码但要不要维护这份代码、怎么维护仍然是你自己的决定。希望这篇实战复盘能帮你少走一些弯路。
返回列表