关于ai开发项目

转载于b站评论区一个叫:deek1的大佬的发言

原文

复制代码
一、缺少整体认知

AI 生成代码时很容易重复造轮子。一个本来可以作为通用能力、和具体业务无关的工具函数或私有方法,AI 往往会直接塞进当前业务模块里,连存放位置和命名都和业务强绑定。结果就是,同样的功能在不同模块里重复实现。



我觉得根本原因还是上下文不足。Codex、Claude Code 这类 Agent 面对大型项目时,不可能每次读取整个代码仓库,只能优先读取当前文件、附近代码以及它认为相关的内容。修改已有功能时还好,但新增功能时,它很可能根本不知道项目其他地方已经存在类似实现,于是自然会重新写一遍。



说到底,AI 生成代码时并不知道整个项目是什么样子,只能在自己认为相关的"一亩三分地"里刨地。你可以告诉它"先搜索已有实现""优先复用公共能力",但如果相关代码根本没有进入它的上下文,再好的规则也没用。



这也是 AI 最容易把大型项目慢慢写成"屎山"的原因之一:它非常擅长解决局部问题,但缺乏对整个代码库的全局认知。

二、 过度防御



AI 生成的代码往往带着一种非常明显的"防御性风格":到处判空、到处加默认值、到处 try-catch,读取环境变量和配置时还特别喜欢 .trim()、fallback 和各种兜底。



这些代码表面上看更加安全,实际上很多时候是在"和稀泥"。一个配置本来就必须存在,没有配置就应该直接报错,AI 却喜欢给一个默认值;一个参数明明已经在上游校验过,它还会在下游再判一次 null;一个异常本来应该交给 Global Exception Handler 统一处理,它却自己先 catch 掉。最后代码确实"不容易报错"了,但真正的问题也被掩盖了。



我觉得主要有两个原因。第一还是上下文缺失导致的局部自保。Agent 不知道项目有没有统一异常处理、不知道参数是否已经校验、不知道数据库是否已经保证字段非空,所以最保险的方式就是保证自己写的这几十行代码"不炸"。



第二是训练语料的影响。模型看过大量开源代码、Stack Overflow、教程和示例,而这些代码通常追求"复制过去就能跑",自然会大量使用判空、try-catch 和默认值。这种写法放在 Demo 里没问题,但搬进一个成熟的商业项目,就很容易破坏原本的架构约束。



所以 AI 写小型 MVP、Demo 时往往如鱼得水,但进入大型项目后,很容易满屏都是没有必要的防御性代码。

3. 过度设计、过度抽象



AI 很喜欢过度设计。



一个原本一个 Service 加几个方法就能解决的问题,它很容易给你拆出接口、抽象类、Factory、Strategy、Builder、Provider、Manager,各种设计模式一股脑往里面塞。



设计模式和抽象本身当然没有问题,问题在于 AI 经常在没有真实扩展需求的时候提前抽象。现在明明只有一个实现,它先抽一个接口,"方便未来扩展";现在只有一种处理逻辑,它先上策略模式,"方便以后增加更多策略"。



最后一个本来很简单的功能,被拆成很多层。业务代码可能只有几十行,但为了支撑这些业务代码,又多出来几百行所谓的"架构代码"。看起来很专业,真正维护的时候却需要在接口、实现类、Factory 和 Strategy 之间来回跳。

4. 代码缺少自明性



AI 生成的代码还有一个很明显的问题:单独看每一行好像都没问题,但整体非常难理解。



各种辅助方法、中间变量、边界判断、封装和抽象堆在一起以后,代码会缺少一条清晰的主线。你很难一眼看出来数据从哪里来、经过了什么处理、为什么需要这个判断、为什么这里要 catch、为什么这个逻辑又被拆到另外一个类。



最明显的感受就是:一碰就碎。



你看到一个 null 判断,觉得好像没有必要,但不敢删;看到一个辅助方法,觉得可以直接合并,但也不敢动;看到一个抽象层明显多余,却不知道删掉以后会不会影响别的逻辑。



最后就会出现一种很讽刺的情况:AI 写出来的代码,还需要再让 AI 给你解释。 下一次修改又继续让 AI 在原来的代码上往上叠,时间久了,人对项目代码的控制力反而会越来越弱。



我一直觉得好的代码应该具有很强的自明性。顺着代码往下读,就应该大概知道数据从哪里来、现在正在做什么、为什么这么做、最后去了哪里。



好的代码像文章,甚至像诗一样,结构清楚、表达自然。而不是每一行看起来都没错,但放在一起以后像一坨东西,没人敢碰。



AI 的这些抽象很多时候并不是从真实业务变化中自然演化出来的,而是因为它见过大量"最佳实践",所以会本能地套设计模式。



好的抽象应该来自真实重复,好的架构应该来自真实变化,而不是一句"以后可能会用到"。

5.重复造轮子和过度设计,在 AI 的世界里好像是自相矛盾的。



它没办法知道一个方法、一个工具以后到底会不会被复用,于是经常出现一种"左右脑互搏"的情况:一边重复造轮子,一边又过度设计,憋了半天最后拉出一坨大的。



在和 AI 进行需求确认和拷问时,我们通常只会确认业务需求和大方向的代码设计,很少会细化到具体的实现细节,比如类、方法、变量怎么定义、怎么命名。



这其实也符合日常业务系统开发的方式。做业务功能时,我们通常重点沟通和确认的是需求本身以及整体代码设计,剩下的实现细节交给 AI 自由发挥。



只有在写脚手架、通用工具库、中间件这类东西时,才会更重点关注代码本身的实现细节。



这两种场景的方向本来就不一样:一个是在实现业务功能,关注点是业务;另一个是在写一段可复用的代码,关注点是代码本身。

这些问题目前存在无解的状态,丢给ai去生成约束文档也没什么卵用。然后就进入了,ai生成的代码ai维护

ai开发会出现的问题

维度 核心病灶 根本原因
1. 空间失明 重复造轮子:通用能力与业务强绑定 RAG/Agent 检索半径有限,新增代码时只看周边文件
2. 信任缺失 过度防御:到处判空、兜底、吞异常 不清楚上下游契约与全局拦截器,只能确保当前代码块"不崩"
3. 模式滥用 过度抽象:单实现抽接口、硬套策略/工厂 训练语料充斥"八股文最佳实践",把代码复杂度当成专业度
4. 逻辑碎片 缺乏自明性:辅助方法乱飞,一碰就碎 缺乏业务主干叙事能力,人类失去心智模型(Mental Model)
5. 流程错位 失控的自由度:只对齐需求,细节放飞 业务开发重交付轻实现,导致 AI 生成的代码只能靠 AI 维护

除了上面这五点,服务端项目进入深水区后,通常还会暴露出另外 3 个典型问题:

  • 6. 事务与 I/O 边界失明(性能与一致性隐患): AI 非常喜欢把逻辑封装进名字好听的私有方法里,结果经常在 @Transactional(数据库事务)内部偷偷塞进 RPC 调用、Redis 操作或消息发送;或者在循环里调用封装好的查询方法,制造极其隐蔽的 N+1 查询灾难。

  • 7. "打补丁式"类型适配(契约腐化): 当上下游数据结构不一致时,人类工程师会去思考"是不是上游 DTO 定义错了",而 AI 为了不改动其他文件,极容易在当前方法里写一堆胶水转换代码(硬转类型、拼装 Map),导致系统里的领域模型越来越脏。

  • 8. 单元测试的"自我洗白"(固化屎山): 让 AI 补单测时,它会根据自己写出的那坨过度防御的代码来写 Mock。代码里写了无意义的 if (x == null),它就专门写个单测验证 x == null 时的兜底逻辑------把本不该存在的防御逻辑,用测试用例永久焊死在项目里。

三条规则:

  1. 新增能力前必须搜索仓库中的相似实现和现有扩展点。

  2. 优先复用语义与契约一致的公共能力,不得仅因代码形式相似就强行抽象。

  3. 如决定新增实现,交付时说明未复用已有代码的原因。

结论:能缓解 30% 的问题(尤其是第 3 条非常聪明),但无法根治,甚至会在上下文变长后迅速失效。

原因在于大模型的工作机制:

  • 第 1 条败在"语义鸿沟": 你让 AI 搜索相似实现,但 AI 怎么搜?它靠 grep 关键词或文件名。如果业务需要"计算折扣后金额并保留两位小数",而你们公共库里叫 MoneyUtil.centToYuanWithRound,AI 搜索 discount 或 price 根本搜不到,它搜完一圈觉得没有,还是会自己写。

  • 第 2 条败在"主观形容词": 什么是"强行抽象"?什么是"过度防御"?对 AI 来说,它写一个 Strategy 模式时,真心觉得这是"合理抽象"而非"强行抽象"。在 Prompt 里用抽象形容词做负面约束(不要过度、不要强行),对 AI 基本等同于废话。

  • 第 3 条真正有效(强制显式推理): 要求它在输出时"说明未复用的原因",相当于强迫它在生成代码前做一次显式的验证输出(Chain-of-Thought)。这能有效遏制它"偷懒不看直接写"的冲动。

三、 业界服务端项目的破局方案:如何按点精准治理?

要跳出"AI 写屎山 -> AI 解释屎山 -> AI 维护屎山"的死循环,核心思路只有一句话:把"道德倡议"变成"数字化硬约束",把"一步生成"拆成"契约对齐 + 填空实现"。

1. 破局"重复造轮子":别让 AI 盲搜,给它一张"能力地图"

与其在 AGENTS.md 里让 AI "每次去搜整个仓库",不如在 AGENTS.md(或专门的 docs/CAPABILITIES.md)里维护一份极简的公共能力索引表(不超 100 行)。

  • 做法: 直接告诉它项目里有哪些现成的轮子、路径在哪、干什么用的。

  • 示例指令: "本项目所有通用能力均收敛在 pkg/common(或 xxx-common 模块)下:日期处理必须用 DateKit,金额计算必须用 Money,分布式锁必须用 @DistLock,HTTP 客户端必须用 Feign/RestTemplateWrapper。严禁在业务 service 包下自行编写上述工具方法。"

2. 破局"过度防御":明确划分"信任边界(Trust Boundary)"

AI 到处判空和 try-catch,是因为它不知道哪里是安全的。你必须在规范里帮它划定架构的信任边界,并启用 Fail-Fast(快速失败) 原则。

  • 量化规则替代口头说教:

    • 异常边界: "Service 层和 DAO 层严禁使用 try-catch 吞掉异常或仅打日志 ,所有业务异常直接抛出 BizException,底层异常直接向上抛给 GlobalExceptionHandler。只有调用第三方外部接口且存在明确降级逻辑时才允许 try-catch。"

    • 判空边界: "Controller 入参通过 @Valid 校验后,进入 Service 层的非空字段严禁二次判空。"

    • 配置边界: "读取环境变量或配置项时,除非需求明确指明默认值,否则严禁提供 fallback 默认值,缺失配置必须让应用在启动期或调用期直接报错。"

3. 破局"过度设计":用"数字死线"封杀设计模式

不要告诉 AI "不要过度设计",要直接剥夺它随便建接口和类的权力。

  • 量化死线(Rule of Three):

    • "禁止单实现接口: 除非是跨模块 RPC 契约或明确要求的多实现扩展点,否则只有 1 个实现类时,严禁创建 Interface(直接写具体 Service 类)。"

    • "禁止提前上设计模式: 分支逻辑少于 3 个时,必须直接写 if-else 或 switch,严禁引入 Strategy、Factory、Builder 模式。"

    • "文件数量限制: 单次需求变更,如无明确架构指令,新增类文件不得超过 X 个。"

4. 破局"缺少自明性":强制"主干目录化(Orchestration Pattern)"

为什么 AI 写的代码看着像一坨?因为它把业务主流程和底层细节混在同一层,或者拆成十几个平级的私有方法互相调。

  • 结构化约束: 要求业务入口方法必须写成"说明书/目录"样式。

    • 主方法只做编排: 一个核心业务入口方法(如 createOrder),内部只能有清晰的步骤调用(如:1. 校验库存 -> 2. 计算价格 -> 3. 落库 -> 4. 发消息),主干代码必须让人在 10 秒内看懂数据流向。

    • 限制私有方法嵌套深度: 明确规定"类内部私有辅助方法调用深度不得超过 1 层(即 public 方法调 private 方法,private 方法严禁再调另一个 private 方法)"。这招能瞬间治好 AI 喜欢乱拆碎方法导致跳转地狱的毛病。

5. 破局"需求与实现脱节":分而治之的正确姿势------"签名先行(Signature-First)"

你在第 5 点提到:"做业务功能时,我们只确认需求和整体设计,不会细化到类、方法怎么定义。" ------ 这正是人失去控制权的转折点。

在 AI 时代,人类不需要写代码,但必须守住"方法签名与数据结构"这一关。业界最有效的"分而治之"不是把功能拆小让 AI 写,而是把生成过程拆成两步:

  1. 阶段一:只生成"骨架与签名"(不写实现代码,耗时 30 秒)

    • 让 AI 先输出:本次准备修改哪些文件、复用哪些已有方法、新增哪几个方法(只写方法名、入参、返回值、一行注释,方法体留空 TODO)。

    • 人类介入点: 你扫一眼只需 15 秒。一眼就能看出:"哎,这个工具方法不用新建,CommonUtil 里有"、"为什么搞了个 Factory?删掉,直接合进 Service 里"。

  2. 阶段二:锁定骨架,填空实现

    • 骨架确认后,再让 AI 往方法体里填肉。因为外框已经被你焊死了,它想过度抽象都没地方建类,想重复造轮子也被你指明了复用路径。

四、 可直接落地的 AGENTS.md 实战模板

你可以把你原来的三条规则,结合上述"量化死线"和"分步卡点",升级为下面这段可直接放进项目根目录 AGENTS.md / CLAUDE.md 的硬核约束:

Markdown

复制代码
## 核心编码铁律(反熵增规范)

### 1. 复用与检索(防重复造轮子)
- **公共能力地图**:通用工具必须优先从 `common/utils`(按你项目实际路径修改)复用。重点关注:`DateUtils`(时间)、`MoneyUtils`(金额)、`JsonUtils`(序列化)、`AssertUtils`(断言)。
- **禁止业务内联工具**:严禁在业务 Service/Controller 内部编写与特定业务无关的字符串处理、日期转换、加解密、集合操作等私有工具方法。
- **交付强制自证**:每次提交方案前,必须输出【复用清单】(复用了哪些已有类/方法)与【新增清单】(为什么无法复用现有能力而必须新增)。

### 2. 极简与反设计模式(防过度抽象)
- **禁止单实现接口**:若某个逻辑当前只有 1 种实现,直接编写具体类(Concrete Class),严禁为了"未来扩展"提前创建 Interface、Abstract Class。
- **事不过三原则(Rule of Three)**:分支逻辑少于 3 种时,强制使用 `if-else` 或 `switch` 直白表达,严禁引入 Strategy、Factory、Builder、Provider 等设计模式。
- **控制调用栈深度**:同一个类内部,`public` 方法可调用 `private` 辅助方法,但 `private` 方法严禁再嵌套调用其他 `private` 方法,保持逻辑扁平。

### 3. 信任边界与 Fail-Fast(防过度防御)
- **禁止吞异常**:业务层严禁使用 `try-catch` 捕获异常后仅打印日志或返回默认值。所有非预期异常直接向上抛出,交由全局异常处理器处理。
- **禁止防御性兜底**:读取配置文件或环境变量时,若非需求明确指定的默认行为,严禁添加 fallback 默认值(配置缺失应直接报错暴露)。
- **禁止冗余判空**:已通过入参校验注解(如 `@Valid`/`@NotNull`)或上游契约保证非空的对象,在下游私有方法中严禁重复进行 `null` 检查。

### 4. 两阶段交付工作流(强制执行)
- 收到新功能或复杂修改需求时,**第一步严禁直接输出完整实现代码**。
- 必须先输出【变更契约设计】:包含拟复用方法、拟修改/新增的类名、方法签名(入参/出参)及主干步骤伪代码,待我确认后,再编写具体方法体实现。

最后一个工程层面的兜底建议:用确定性工具对抗概率性模型

Prompt 永远是软约束(概率性的),而架构腐化需要硬约束(确定性的)来拦截。在服务端项目里,最省心的做法是配合静态检查工具做 CI/本地 Hooks 卡点:

  • Java 项目: 引入 ArchUnit (写几行规则代码就能在编译期强制规定:service 包不允许定义 Util 结尾的类、Controller 不允许直接调 DAO、禁止捕获 Exception.class 等)。

  • Go / Python / TS 项目: 配置严格的 Linter(如 golangci-lint 的 cyclop/gocognit 限制圈复杂度和嵌套深度,禁止空的或只打日志的 catch/err 处理)。

当 AI 写完代码跑测试时,如果 ArchUnit 或 Linter 直接报错拦住,它自己就会乖乖把过度防御和违规建类的代码改回来,根本不需要你去费口舌教育它。

AGENTS.md

这份专为 Java 17 + Spring Boot 3 + MyBatis-Plus 技术栈定制的 AGENTS.md(如果你主要用 Claude Code,也可以同时复制一份命名为 CLAUDE.md),已经将前面讨论的防重复造轮子、防过度防御、防过度设计、强制自明性 全部转化为针对 Spring 与 MyBatis-Plus 的硬性数字化铁律。

你可以直接复制到项目根目录,并根据你们实际的包名(搜索替换 com.company.project 及底座类名)做微调:

Markdown

复制代码
# AGENTS.md - 项目全局架构与编码铁律

## 1. 技术栈与基线环境
- **核心环境**: Java 17 + Spring Boot 3.x (请严格使用 `jakarta.*` 命名空间,严禁引入 `javax.*`)
- **持久层**: MyBatis-Plus 3.5.x (优先使用 `LambdaQueryWrapper` / `LambdaUpdateWrapper`,严禁硬编码数据库字段名字符串)
- **基础组件**: Lombok (`@Slf4j`, `@RequiredArgsConstructor`), MapStruct / BeanUtils, Spring Validation (`@Valid` / `@Validated`)
- **Java 17 特性**: 积极使用 `record` 定义不可变 DTO/VO,使用 `switch` 表达式、`var`(仅限局部变量且类型一眼可知时)及 Text Blocks。

---

## 2. 公共能力地图(防重复造轮子)
**【铁律】严禁在业务 `service` 或 `controller` 包下自行编写与业务无关的私有工具方法(如日期转换、金额计算、JSON 解析、加解密、分页组装)。必须且只能复用以下底座:**

- **统一响应体**: `com.company.project.common.model.Result<T>` (`Result.success(data)`, `Result.fail(code, msg)`)
- **业务异常体系**: 
  - 抛出异常:`throw new BizException(ErrorCode.XXX)` 或 `BizAssert.notNull(obj, ErrorCode.XXX)`
  - 全局拦截:已由 `GlobalExceptionHandler` 统一兜底,业务代码无需关心 HTTP 状态码封装。
- **核心工具库 (`com.company.project.common.util.*`)**:
  - **集合与判空**: 优先使用 `org.springframework.util.CollectionUtils`、`StringUtils` 或项目内置的 `CollUtil` / `StrUtil`。
  - **金额计算**: 必须使用 `MoneyUtil`(内部基于 `BigDecimal` 并统一了舍入模式),严禁在业务代码中直接 `new BigDecimal(double)` 或手写精度舍入逻辑。
  - **时间处理**: 统一使用 Java 8+ `LocalDateTime` / `LocalDate` 及 `DateTimeUtil`,严禁使用 `java.util.Date` 和 `SimpleDateFormat`。
  - **JSON 序列化**: 统一使用 `JsonUtil` (底层封装 Jackson),严禁在业务类中自行 `new ObjectMapper()`。
  - **对象拷贝**: 统一使用 `BeanConvertUtil` 或 MapStruct Mapper,严禁手写冗长的百行 `get/set` 转换方法。
  - **上下文获取**: 获取当前登录用户统一调用 `UserContext.getCurrentUserId()`。

---

## 3. 信任边界与 Fail-Fast 规范(防过度防御)
**【铁律】拒绝"和稀泥"式防御代码,有问题必须快速暴露(Fail-Fast):**

1. **禁止乱用 try-catch 吞异常**:
   - `Controller`、`Service`、`Mapper` 层**严禁**使用 `try-catch` 捕获异常后仅打印日志或返回 `null`/空集合。
   - 所有非预期异常直接向上抛出交由 `GlobalExceptionHandler` 处理;业务分支阻断直接抛 `BizException`。
   - **唯一例外**: 调用外部第三方 RPC/HTTP 接口且需求明确要求"降级/容错"时才允许 `try-catch`,且必须在注释中写明降级原因。
2. **禁止无效二次判空**:
   - `Controller` 层入参必须使用 `@Valid` + `@NotNull`/`@NotBlank`/`@NotEmpty` 注解完成校验。
   - 一旦进入 `Service` 层,对于契约上已保证非空的入参属性或数据库非空列,**严禁**再次编写防御性的 `if (param != null)` 嵌套或 `Optional.ofNullable().orElse()` 兜底。
3. **禁止配置项私自兜底**:
   - 使用 `@Value("${xxx.yyy}")` 或 `@ConfigurationProperties` 读取配置时,除非业务需求明确指定默认值,否则**严禁**私自添加 `:defaultValue`(配置缺失必须在容器启动期直接报错)。
4. **MyBatis-Plus 返回值约定**:
   - `Mapper.selectList()` 在 MP 中永远返回非 `null` 的 List(无数据为空集合),下游严禁再写 `if (list != null && !list.isEmpty())`,统一只判断 `CollectionUtils.isEmpty(list)`。

---

## 4. 极简架构与反过度设计(防模式滥用)
**【铁律】好的抽象来自真实重复,严禁为"未来可能扩展"提前透支复杂度:**

1. **废除无意义的单实现接口**:
   - 对于纯内部业务 Service,若当前只有 1 种实现,**直接编写 `@Service public class OrderService`**(或按团队既有习惯保持单层),严禁额外拆分 `Processor`、`Manager`、`Handler`、`Provider`、`Factory` 等中间层接口。
   - 严禁创建只有一个实现类的 `Strategy` 或 `Factory` 模式。
2. **事不过三原则 (Rule of Three)**:
   - 分支逻辑少于 3 种时,必须使用直白的 `if-else` 或 Java 17 `switch` 表达式,**严禁**引入策略模式、工厂模式、模板方法模式或建造者模式。
   - 严禁为了复用三五行偶然相似的代码,强行抽取带大量布尔标识参数(如 `boolean isSpecial, boolean needCheck`)的通用方法。
3. **依赖注入规范**:
   - 统一使用 `@RequiredArgsConstructor` + `private final` 构造器注入,严禁使用 `@Autowired` 字段注入。

---

## 5. 代码自明性与主干叙事(防逻辑碎片与事务灾难)
**【铁律】业务代码必须像目录一样清晰,让人在 10 秒内看懂数据流向:**

1. **入口方法"目录化" (Orchestration Pattern)**:
   - 核心业务 `public` 方法只负责主干流程编排(如:`1. 校验业务状态 -> 2. 组装领域数据 -> 3. 执行数据库落库 -> 4. 发送领域事件`)。
   - **调用栈深度死线**: 同一个类内部,`public` 方法可以调用 `private` 辅助方法,但 **`private` 方法严禁再嵌套调用其他 `private` 方法**(私有方法调用深度上限为 1 层),杜绝在类内部反复横跳。
2. **严守事务与 I/O 边界**:
   - `@Transactional(rollbackFor = Exception.class)` 必须标注在 `public` 方法上,且事务块内只允许包含纯粹的数据库读写与内存计算。
   - **严禁**在 `@Transactional` 方法内部(包含其调用的私有方法内)执行远程 HTTP/RPC 调用、文件 I/O、耗时 Redis 锁等待或同步发送 MQ 消息。
3. **杜绝 N+1 查询**:
   - **严禁**在 `for` 循环或 `stream().map()` 内部调用 `Mapper` 或其他 `Service` 的数据库查询方法。必须先提取 ID 集合,使用 `LambdaQueryWrapper.in(...)` 批量查询并转为 `Map` 后在内存中组装。

---

## 6. 两阶段交付工作流(Agent 强制执行协议)
**当你收到新增功能、复杂重构或跨文件修改指令时,【严禁】直接生成完整业务实现代码!必须严格分两步执行:**

### 第一步:仅输出【变更契约设计】(等待人类确认)
请先在回复中输出以下极简清单:
1. **复用清单**: 本次计划复用的现有公共类、工具方法、Mapper 方法。
2. **未复用说明**: 若涉及通用逻辑但决定自己新写,必须说明为何现有公共库无法满足。
3. **签名与主干设计**: 列出拟新增/修改的类名、完整方法签名(入参、返回值)以及核心方法内部的 `1 -> 2 -> 3` 步骤注释骨架(不含具体实现细节)。

### 第二步:填空实现
待人类确认上述契约与签名无误后,严格在锁定的类与方法签名范围内填充实现代码,不得擅自新增未获批的辅助类、接口或设计模式。

配套大招:用 1 个测试类给 AI 戴上"物理紧箍咒"

正如你之前所说,当上下文变长时,AI 依然有可能把 AGENTS.md 忘在脑后。在 Spring Boot 3 项目里,业界最彻底的做法是在 src/test/java 下加一个 ArchUnit 架构守护测试类。

只需在 pom.xml 引入依赖:

XML

复制代码
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>

然后放一个测试类 ArchitectureGuardTest.java。当 AI 写完代码运行 mvn test 时,只要它敢在 service 包下偷建 Util 类、或者乱建 Factory/Strategy,编译测试直接 RED 报错,逼着 AI 当场改回简单代码:

Java

复制代码
package com.company.project;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

@AnalyzeClasses(packages = "com.company.project", importOptions = ImportOption.DoNotIncludeTests.class)
public class ArchitectureGuardTest {

    // 1. 严禁 AI 在业务 service 或 controller 包下私自创建 Util / Helper 类(防重复造轮子)
    @ArchTest
    static final ArchRule NO_UTILS_IN_BUSINESS_PACKAGES = noClasses()
            .that().resideInAnyPackage("..service..", "..controller..")
            .should().haveSimpleNameEndingWith("Util")
            .orShould().haveSimpleNameEndingWith("Utils")
            .orShould().haveSimpleNameEndingWith("Helper")
            .because("通用工具能力必须统一收敛至 common.util 包,严禁在业务包内重复造轮子");

    // 2. 严禁 Controller 直接跳过 Service 操作 MyBatis Mapper
    @ArchTest
    static final ArchRule CONTROLLER_SHOULD_NOT_ACCESS_MAPPER = noClasses()
            .that().resideInAPackage("..controller..")
            .should().dependOnClassesThat().resideInAPackage("..mapper..")
            .because("Controller 严禁直接注入 Mapper,必须通过 Service 编排");

    // 3. 严禁使用 @Autowired 字段注入,强制构造器注入
    @ArchTest
    static final ArchRule NO_FIELD_INJECTION = noClasses()
            .should().beAnnotatedWith("org.springframework.beans.factory.annotation.Autowired")
            .because("请统一使用 Lombok @RequiredArgsConstructor + private final 进行构造器注入");
}

这套 "AGENTS.md 明确契约与两阶段卡点 + ArchUnit 单元测试物理拦截" 的组合拳,基本能把 Spring Boot 项目里 AI 制造屎山的冲动按死在摇篮里。

清理 AI 遗留的"屎山"代码

清理 AI 遗留的"屎山"代码(到处判空、层层嵌套私有方法、单实现接口、吞异常),最忌讳直接对 AI 说"帮我重构这段代码"。因为 AI 在重构时同样会触发局部防御本能 ------它不敢删自己拿不准的 if (x != null),反而会为了"结构好看"把原来的碎方法再抽成一个 Helper 类,把单文件屎山变成多文件屎山。

要安全拆解"一碰就碎"的代码,核心原则是:先用外部测试锁死输入输出(安全网),再让 AI 先做"外科诊断"(人工确认可删项),最后执行"内联展平 + 信任边界脱水"。

以下是一套经过实战验证的 "四步安全拆弹工作流"与可直接复制的 Prompt 模板:

第一步:建立"黑盒快照测试"(防改崩安全网)

在动刀删任何一行判空或抽象层之前,不能相信原来的单元测试(因为原单测大概率也在测那些无意义的兜底逻辑)。我们需要先针对业务入口方法生成纯业务视角的黄金路径与核心异常测试。

📋 复制给 AI 的 Prompt(阶段 1:锁死核心契约):

Markdown

复制代码
你现在是一名资深 Java 架构师。我准备重构 `@[目标类名.java]` 中的 `[目标入口方法名]`,这段代码充斥着大量过度防御和碎片化封装。
在修改代码前,请先帮我生成一份基于 JUnit 5 + Mockito 的【黑盒契约测试】,用于锁死当前的核心业务行为。

【严格要求】:
1. **只测业务契约,不测防御噪音**:只针对该 public 方法的【正常业务主流程(Happy Path)】和【明确的业务异常分支(抛出 BizException)】编写测试。
2. **禁止为无意义兜底写单测**:代码中那些纯粹为了防崩而写的冗余 null 兜底、静默 catch 返回空集合等逻辑,严禁为其编写测试用例(因为这些垃圾逻辑稍后会被清理)。
3. **只 Mock 外部边界**:只允许 `@Mock` 注入的 Mapper、外部 RPC Client 或第三方组件,严禁使用 `@Spy` 去 Mock 当前类内部的 private/protected 辅助方法。

请直接输出测试类代码。

第二步:外科手术诊断(只列清单,不准改代码)

这是最关键的一步。让 AI 先充当"代码审计员",把它看到的废代码全部扒出来列成表格。你只需要花 30 秒勾选确认,就能徹底消除 AI"不敢删"或"乱删"的风险。

📋 复制给 AI 的 Prompt(阶段 2:冗余诊断与脱水报告):

Markdown

复制代码
请对 `@[目标类名.java]`(及相关联的接口/策略类)进行"反过度设计与反过度防御"深度诊断。
**注意:本阶段严禁输出任何修改后的业务代码!** 请仅输出以下 4 份诊断清单供我审批:

### 1. 【业务主干还原】(10 秒叙事)
忽略所有判空、日志、类型转换和私有方法跳转,用 3~6 个自然语言步骤还原这个方法真正在干的业务主线(数据从哪来 -> 核心计算/校验 -> 落库/副作用 -> 返回什么)。

### 2. 【可安全移除的"过度防御"清单】
请逐行排查并列出以下噪音(注明行号、代码片段、建议删除原因):
- **冗余判空**:上游 `@Valid` 已校验、数据库非空字段、或 `MyBatis-Plus selectList` 返回值被重复 `!= null` 检查的地方。
- **违规吞异常**:私自 `try-catch` 打日志返回默认值/null,掩盖了本该抛给 `GlobalExceptionHandler` 的异常。
- **私自配置兜底**:没有业务依据的 `Optional.ofNullable().orElse(默认值)`。

### 3. 【可安全坍缩的"过度抽象与碎片方法"清单】
- **碎片化 private 方法**:列出所有调用深度超过 1 层、或者仅仅为了几行代码而拆出的"传话筒式"私有方法,标注哪些可以直接 Inline(内联)回主干。
- **伪设计模式**:如果关联代码中存在"只有 1 个实现类的 Interface/Strategy/Factory/Builder",请全部揪出来列入拆除名单。

### 4. 【可替换为公共底座的"重复轮子"】
对照项目 `AGENTS.md` 中的公共能力地图,指出代码中哪些手写的日期转换、金额精度处理、集合判空、Bean 转换可以直接替换为公共工具类或 `LambdaQueryWrapper`。

第三步:执行"内联展平与脱水重构"

当你扫完第二步 AI 输出的清单,回复一句"清单确认无误(或:保留第 X 项,其余全部按清单清理)"后,发送下面这段重构指令。

这里用到了重构领域最克制 AI 乱拆方法的一招:先强制内联(Inline)拉平,再按主干目录重新切分。

📋 复制给 AI 的 Prompt(阶段 3:执行重构):

Markdown

复制代码
诊断清单已确认。现在请严格按照上述确认的清单,对 `@[目标类名.java]` 执行代码瘦身与重构。

【重构铁律】:
1. **先内联展平,再目录化编排**:
   - 将清单中碎裂的多层 `private` 辅助方法全部内联(Inline)收回。
   - 入口 `public` 方法必须改写为清晰的"目录式结构"(步骤 1、步骤 2、步骤 3...),让人顺着往下读 10 秒内看懂数据流向。
   - 如确需抽取 `private` 辅助方法,必须代表一个完整的业务子步骤,且**严禁 `private` 方法再嵌套调用另一个 `private` 方法**(调用栈深度锁死为 1 层)。
2. **彻底清除防御噪音**:
   - 删掉清单中确认的所有冗余 `null` 判断、无意义默认值和吞异常的 `try-catch`。
   - 遇到业务阻断统一使用项目标准断言或直接 `throw new BizException(...)`。
3. **利用 Java 17 与 MyBatis-Plus 特性提纯**:
   - 用 Java 17 `record` 或简洁的 `Stream` / `switch` 表达式替换冗长的中间变量拼装。
   - 检查是否存在循环内查库(N+1 问题),如有,必须改写为提前批量 `in` 查询转 `Map` 内存组装。
4. **保持外部契约绝对不变**:
   - 严禁修改入口方法的签名、入参 DTO 结构和返回值类型,确保阶段 1 生成的单元测试 100% 编译通过且绿灯。

请输出重构后的完整代码,并在末尾简要统计:本次重构净减少了多少行代码、消灭了几个私有方法/多余类。

第四步:一个直观的"脱水"前后对比示例

让你感受一下这套 Prompt 跑出来的实际效果:

❌ 重构前(典型的 AI 遗留代码:120 行、3 层私有跳转、到处防崩):

  • 入口方法 cancelOrder(req) 先判空 req,再调 validateCancelRequest(req);

  • validateCancelRequest 里又判空 req.getOrderId(),再去调 fetchOrderSafely(orderId);

  • fetchOrderSafely 里包了一层 try-catch (Exception e),查不到或者报错就 log.error 然后返回 null;

  • 主方法拿到 null 后不报错,居然默默 return Result.fail("系统繁忙");

  • 接着为更新订单状态,专门抽了一个 OrderStatusUpdateStrategyFactory.getStrategy(type).update(...),点进去发现整个项目其实只有 1 个实现类。

✅ 经过上述三步 Prompt 脱水重构后(30 行、一眼到底、Fail-Fast):

Java

复制代码
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(CancelOrderReq req) {
    // 1. 获取并校验订单状态 (@Valid 已保证 req.orderId 非空,无需二次判空)
    OrderDO order = orderMapper.selectById(req.orderId());
    BizAssert.notNull(order, ErrorCode.ORDER_NOT_FOUND);
    BizAssert.isTrue(order.canCancel(), ErrorCode.ORDER_STATUS_INVALID);

    // 2. 执行订单状态流转与库存回滚
    order.markAsCancelled(req.reason(), UserContext.getCurrentUserId());
    orderMapper.updateById(order);
    inventoryService.releaseStockBatch(order.getId());

    // 3. 记录操作审计日志
    orderLogMapper.insert(OrderLogConvert.toCancelLog(order));
}
  • 自明性拉满:数据从哪来、校验了什么、改了什么、去了哪里,顺着读 5 秒钟一目了然。

  • 没有一处多余的 if (x != null) 和 try-catch :数据库连不上直接抛给 GlobalExceptionHandler,订单不存在直接由 BizAssert 阻断。

实战小贴士:如何把这套重构固化成快捷指令?

如果你使用的是 Claude Code 或 Cursor:

  • Claude Code :可以在项目根目录创建 .claude/commands/debloat.md,把上面阶段 2 + 阶段 3 的 Prompt 放进去。以后看到恶心代码,直接在终端敲 /project:debloat OrderService.java,它就会自动先给你出诊断表,等你敲回车确认后再一键脱水。

  • Cursor :可以保存在 .cursor/rules/refactor-debloat.mdc 中,需要瘦身时直接 @refactor-debloat 召唤即可。

相关推荐
了不起的云计算V1 小时前
MobileAI×WorkBuddy,一场算力与应用的“双向奔赴”
人工智能
小海测评1 小时前
生成式引擎优化技术解析:大语言模型外部内容采信机制与工程实践
大数据·人工智能·语言模型·geo·企业获客
yyzc111111 小时前
合规稳定资金结算收款系统助力门店发展
大数据·运维·人工智能
长不大的杨瑞杰1 小时前
OpenCode v2 运行时预热实战:Warming、Prompt Cache、后台服务与成本治理
人工智能·安全·云计算·软件工程·devops
晓天衡宇•评测社区1 小时前
当需求具体到图片尺寸,Nano Banana 2.1 交出了什么答案?
人工智能·计算机视觉
fruge1 小时前
没有独显也想做AI配音?我用MOSS-TTS-Nano在CPU电脑上跑通了本地语音生成
人工智能·电脑
天树星智源1 小时前
静态物理满分、交互 11.54%:3D 场景生成系统的幻觉
论文阅读·人工智能·3d·机器人
OpenAnolis小助手2 小时前
多租户公有云共享 CXL 内存池场景下的内存卸载研究
人工智能·操作系统·龙蜥社区·risc-v·agentic os
xsd202411182 小时前
OCEC 24×40 开闭眼脑电分类:从 α 波节律到实时 BCI 落地方案
人工智能·1024程序员节