AI总写“外包代码”?一份高阶 Skills 优化方案让 Cursor 瞬间懂行

文章目录

    • [一、 为什么 AI 写的代码总像"外包"?------认识 Skills 规则文件](#一、 为什么 AI 写的代码总像“外包”?——认识 Skills 规则文件)
      • [新手常遇的痛点:AI 写的代码总像"外包"](#新手常遇的痛点:AI 写的代码总像“外包”)
      • [什么是 Skills(规则文件)?](#什么是 Skills(规则文件)?)
    • [二、 划定技术栈边界:别让 AI 用老黄历写新代码](#二、 划定技术栈边界:别让 AI 用老黄历写新代码)
      • [明确 JDK 版本与依赖库:告别"跨版本穿越"](#明确 JDK 版本与依赖库:告别“跨版本穿越”)
      • 指定构建工具与插件限制:统一"施工流程"
      • [禁用清单:明确告诉 AI "不要用什么"](#禁用清单:明确告诉 AI “不要用什么”)
    • [三、 绘制项目的"建筑蓝图":用架构约束锁定代码流向](#三、 绘制项目的“建筑蓝图”:用架构约束锁定代码流向)
      • [为什么 AI 总是把业务逻辑写到 Controller 里?](#为什么 AI 总是把业务逻辑写到 Controller 里?)
      • [使用 Mermaid 流程图向 AI 描绘架构](#使用 Mermaid 流程图向 AI 描绘架构)
    • [四、 统一团队"黑话":让 AI 听懂你的业务领域语言](#四、 统一团队“黑话”:让 AI 听懂你的业务领域语言)
    • [五、 设定不可逾越的"红线":异常处理与日志规范](#五、 设定不可逾越的“红线”:异常处理与日志规范)
      • [告别 `e.printStackTrace()`:统一日志框架与占位符用法](#告别 e.printStackTrace():统一日志框架与占位符用法)
      • 异常分类哲学:业务异常与系统异常的严格区分
      • [校验规则的统一:何时用 `@Valid`,何时手动抛异常](#校验规则的统一:何时用 @Valid,何时手动抛异常)
    • [六、 摆脱说教,直接上"满分试卷":注入标杆代码示例](#六、 摆脱说教,直接上“满分试卷”:注入标杆代码示例)

一、 为什么 AI 写的代码总像"外包"?------认识 Skills 规则文件

在大多数程序员刚开始用 Cursor、Claude Code 或者 Trae 这类 AI 编程助手时,通常都会经历这样一个心路历程:
"哇,一下就跑通了!" ➡️ "等等,这代码怎么写的?" ➡️ "删掉,我自己重写吧。"

新手常遇的痛点:AI 写的代码总像"外包"

为什么会有这种感觉?因为默认状态下的 AI,就像是一个精通百门语言的"自由职业者"。你让它写一个保存用户的功能,它确实能帮你实现,但它会暴露出以下问题:

  • 乱用工具包 :你项目里明明统一用了 org.apache.commons.lang3 的字符串处理,它偏要给你手写一段 java.util 的底层逻辑,或者引入你项目里根本不存在的包。
  • 毫无章法的分层:你明明规划了严格的 Controller、Service、Mapper 三层架构,它为了图省事,直接在 Controller 里写了一大堆 SQL 查询逻辑。
  • 变量命名随意 :你团队里统一定义了用户余额叫 balance,它给你来个 money 或者 userMoney,导致后续维护时到处都是同义词。
    它能干活,但它不遵守你们团队的"江湖规矩"。这种代码一旦合并到主干,就像一件精心定制的西装上打了个五颜六色的补丁。

什么是 Skills(规则文件)?

要解决这个问题,就不能把 AI 当成"工具",而要把它当成一个刚入职的新人程序员

想象一下,一个新员工第一天来上班,你肯定不会直接把他按在工位上说:"开始写代码"。你会给他发一份**《员工手册》 《项目开发规范》
在 AI 编程工具里,Skills 规则文件就是这份
员工手册**。

它是一个纯文本文件(通常是 Markdown 格式),你在里面写下硬性规定。当 AI 准备生成代码或者修改代码时,它会先"翻阅"一遍这份手册,然后严格按照手册里的规矩来敲键盘。

比如,你在手册里写了一条:"所有时间类型必须使用 java.time.LocalDateTime,禁止使用 java.util.Date。" 那么 AI 就像一个背熟了条文的员工,绝不敢在你的代码里写出一个 Date 来。

二、 划定技术栈边界:别让 AI 用老黄历写新代码

AI 是个刚入职的新人。但如果这个新人虽然看了《员工手册》,却拿着十年前的技术栈来写代码,那同样是一场灾难。

很多开发者会疑惑:"我明明用的是最新的 Spring Boot 3,为什么 AI 还在给我写 javax.servlet 的代码?"

这是因为,AI 的肚子里装了从 Java 1.5 到 Java 21 的所有知识,它不知道你当前项目的"时间线"在哪里。如果你不划定技术栈的边界,它就会凭概率输出它见过最多的"老黄历"代码。

明确 JDK 版本与依赖库:告别"跨版本穿越"

在 Java 世界里,版本的跨越往往意味着不兼容。最典型的就是 Spring Boot 2.x 到 3.x 的升级,底层从 Java EE 迁移到了 Jakarta EE,导致大量的包名发生了变化。

如果你不告诉 AI 你的版本,它可能会在你的 Spring Boot 3 项目里写出这样的导入:

java 复制代码
// 错误示例:这是 Spring Boot 2.x (老黄历) 的写法
import javax.validation.Valid;
import javax.servlet.http.HttpServletRequest;

当这段代码粘进你的 IDE 里,满屏飘红。为了防止这种"穿越",我们需要在 Skills 规则文件里直接把版本和依赖锁死:

markdown 复制代码
## 技术栈版本约束
- JDK 版本:严格使用 JDK 17,禁止使用 JDK 8 特性(如不使用 java.util.Date,统一用 java.time)。
- 核心框架:Spring Boot 3.2.0。
- 依赖包命名空间:由于是 Spring Boot 3.x,所有 Web 和校验相关的包名必须是 Jakarta 命名空间。

有了这段话,AI 再写代码时,就会自动纠正为:

java 复制代码
// 正确示例:符合 Spring Boot 3.x 规范
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpServletRequest;

指定构建工具与插件限制:统一"施工流程"

现在的 Java 项目大多用 Maven 或 Gradle 构建。虽然它们都能把项目跑起来,但在写法和插件使用上大有不同。特别是现在越来越多的项目开始引入 Lombok 或者 MapStruct 这类注解处理器。

如果不加限制,AI 可能会在 POM 文件里乱加一些你团队根本不用的插件,或者漏掉关键的配置。

你可以在规则文件中这样规定:

markdown 复制代码
## 构建工具规范 (Maven)
- 本项目使用 Maven 作为构建工具,禁止生成 build.gradle 相关文件。
- 依赖管理:所有的版本号必须在 <dependencyManagement> 中统一管理,子模块禁止直接写死版本号。
- 必须包含的编译插件:
  - maven-compiler-plugin(配置 source 和 target 为 17)
  - lombok(必须在 maven-compiler-plugin 的 annotationProcessorPaths 中配置,不要指望全局依赖生效)

当你要求 AI 帮你新增一个模块或者引入 MyBatis-Plus 时,它就不会简单地扔给你一个 <dependency> 标签,而是会严格按照你规定的格式,把版本号放在正确的地方,并处理好 Lombok 的编译依赖。

禁用清单:明确告诉 AI "不要用什么"

在约束人的时候,我们常说"要做什么",但在约束 AI 时,"不要做什么"往往比"要做什么"更有效力 。AI 很聪明,但也喜欢走捷径。有些技术在语法上完全正确,但被你所在的团队明令禁止。

这时候,一份"黑名单"就非常重要:

markdown 复制代码
## 绝对禁用清单(红灯区)
遇到以下情况,即使代码能跑通,也绝对不允许生成:
1. 禁用 JPA 的自动建表功能(禁止出现 `spring.jpa.hibernate.ddl-auto=update` 或 `create`)。
2. 禁止使用 `System.out.println()` 打印日志,必须使用 Slf4j。
3. 禁止使用 `fastjson`(存在安全漏洞),所有 JSON 序列化统一使用 `Jackson` 或 `com.alibaba.fastjson2`。
4. 禁止在代码中捕获 Exception 后空处理(即 `catch (Exception e) {}` 这种毫无意义的吞异常行为)。

为什么需要这么强硬?因为当你问 AI "帮我把这个对象转成 JSON" 时,如果没设禁令,它大概率会因为它训练数据里 fastjson 的 API 最短、最简单,就直接给你 JSON.toJSONString()。一旦引入,不仅违反团队规范,还可能有很大的安全隐患。
总结一下,划定技术栈边界,本质上就是在告诉 AI:"这是我们的武器库,你可以用这里面最新的枪炮,但绝对不准用外面捡来的土铳。" 只有边界清晰了,AI 写出来的代码才能直接无缝融入你的项目。

三、 绘制项目的"建筑蓝图":用架构约束锁定代码流向

你肯定遇到过这种让人抓狂的场景:你让 AI "写一个根据用户 ID 查询订单的接口",结果它洋洋洒洒给你返回了一段代码,所有的查库逻辑、数据组装、甚至金额计算,全塞在了一个 Controller 的方法里,方法长度眼看就要突破一百行。

为什么 AI 总是把业务逻辑写到 Controller 里?

这其实不能全怪 AI,我们要理解它的"求生欲"。

AI 的底层逻辑是预测下一个最可能出现的词 。在它看过的海量开源代码、个人博客和 StackOverflow 问答中,为了"快速演示如何调用一个接口",无数开发者都习惯把所有逻辑写在 Controller 里。

对 AI 来说,把逻辑写在 Controller 里是"最保险、最常见"的输出方式,因为它无法通过肉眼看出你的项目其实有着严格的架构规范。它没有"全局观",就像一个盲人摸象的泥瓦匠,你让他砌一堵墙,他就真的一直砌,根本不管这堵墙会不会把大厦的承重墙给堵死。

要解决这个问题,我们不能只用文字去干巴巴地解释"请遵守三层架构",我们需要直接把项目的建筑蓝图贴在它的工位上。

使用 Mermaid 流程图向 AI 描绘架构

人类是视觉动物,其实 AI 也是。现代的编程助手(如 Claude Code、Cursor)对 Mermaid 流程图的理解能力极强。在 Skills 规则文件中,画一张清晰的架构图,胜过你写一千字的解释。

假设我们使用的是经典的 Java 三层架构,你可以在规则文件里直接写入以下内容:

markdown 复制代码
## 项目架构蓝图
本项目严格遵循以下分层架构,任何代码生成必须符合此流向:
```mermaid
graph TD
    Client((客户端)) --> Controller[表现层 Controller]
    Controller -->|传输对象 DTO| Service[业务逻辑层 Service]
    Service -->|领域对象 DO| Mapper[数据访问层 Mapper/Repository]
    Mapper --> Database[(MySQL 数据库)]
    
    classDef controller fill:#f9f,stroke:#333,stroke-width:2px;
    classDef service fill:#bbf,stroke:#333,stroke-width:2px;
    classDef mapper fill:#bfb,stroke:#333,stroke-width:2px;
    
    class Controller controller;
    class Service service;
    class Mapper mapper;

架构职责定义:

  • Controller 层:仅负责接收请求、参数校验(@Valid)、调用 Service、返回响应。禁止包含任何业务计算和数据库查询。

  • Service 层:核心业务逻辑所在。负责事务管理(@Transactional)、对象转换(DTO 与 DO 互转)、业务规则校验。

  • Mapper 层:仅负责与数据库交互,禁止出现业务逻辑判断(比如不要在 SQL 的 WHERE 里写复杂的业务状态机判断)。

    当你把这段图表丢给 AI 后,它的脑子里就不再是模糊的文字,而是一张清晰的"地图"。当你再让它写功能时,它会自动对号入座,知道哪段代码该放在哪个"房间"里。

    规定层与层之间的数据流转与依赖方向

    光有架构图还不够,AI 经常会在"数据流转"上犯错。比如它可能会在 Service 层直接返回数据库实体类(DO)给 Controller,导致敏感字段(如用户密码)被直接暴露给前端;或者它为了省事,在 Controller 里直接注入 Mapper 跳过 Service。
    我们需要在规则文件中制定严格的**"交通规则"**:

    markdown 复制代码
    ## 层级依赖与数据流转红线
    1. **单向依赖原则**:只能从上层调用下层(Controller -> Service -> Mapper)。
       - ❌ 绝对禁止:Controller 直接注入 Mapper。
       - ❌ 绝对禁止:Mapper 反向调用 Service。
    2. **对象隔离原则**:不同层之间传递数据,必须使用对应的对象,严禁混用。
       - Controller 接收和返回:`xxxDTO` (Data Transfer Object)
       - Service 内部处理:`xxxDO` (Data Object,对应数据库表结构)
       - 转换工具:DTO 与 DO 的转换必须使用 `MapStruct` 生成的 Converter,禁止手写 Getter/Setter 循环赋值。

有了这两条"红线",我们来看看当遇到实际需求时,AI 的表现会发生怎样脱胎换骨的变化。
错误示范(没有架构约束时的 AI 输出):

java 复制代码
import org.apache.ibatis.session.SqlSessionFactory; // 违反红线:Controller直接引入了底层数据库组件
import com.example.demo.entity.UserDO; // 违反红线:直接返回了数据库实体
@RestController
public class UserController {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory; // 违反红线:跳过了Service和Mapper
    @GetMapping("/user/{id}")
    public UserDO getUser(@PathVariable Long id) {
        // 违反红线:业务逻辑写在了Controller里
        try (var session = sqlSessionFactory.openSession()) {
            var user = session.selectOne("com.example.demo.mapper.UserMapper.selectById", id);
            if (user.getAge() < 18) { // 违反红线:业务判断混入Controller
                throw new RuntimeException("未成年人禁止访问");
            }
            return user; // 违反红线:数据库实体直接暴露给前端,密码等字段会泄露
        }
    }
}

正确示范(加入架构蓝图与流转规则后的 AI 输出):

java 复制代码
import com.example.demo.dto.UserDTO;
import com.example.demo.dto.UserInfoResponse;
import com.example.demo.service.UserService;
import com.example.demo.converter.UserConverter;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class UserController {
    // 符合规范:只依赖 Service 层
    private final UserService userService;
    // 符合规范:使用 MapStruct 生成的转换器
    private final UserConverter userConverter;
    @GetMapping("/user/{id}")
    public UserInfoResponse getUser(@PathVariable @Valid Long id) {
        // 符合规范:Controller 极度轻量,只做调用和转换
        UserDTO userDTO = userService.queryUserById(id);
        return userConverter.toResponse(userDTO);
    }
}

通过给 AI 提供一张架构图和两条简单的依赖红线,你其实就是在告诉它:"在这个工地上,搬砖的去搬砖,和水泥的去和水泥,谁也别越界干别人的活。" 这样生成的代码,才真正具备企业级项目的可维护性。

四、 统一团队"黑话":让 AI 听懂你的业务领域语言

在真实的业务开发中,每个公司、每个团队都有一套自己的"黑话"。

比如,你们公司做的是电商,系统里的"商品"可能不叫 Product,而叫 SKU 或者 SPU;做金融系统的,"账户"可能不叫 Account,而叫 Ledger(账本)或者 Wallet(钱包)。

当你对着 AI 说:"帮我写一个接口,冻结这个用户的账户",如果 AI 不懂你们的黑话,它就会展现出浓烈的**"翻译腔"**。

避免"翻译腔":别让 AI 当生硬的同声传译

"翻译腔"是 AI 写业务代码时最容易被忽略,却也最让人难受的毛病。

假设你们团队的实体类叫 MerchantFundDetail(商户资金明细),你让 AI 写一段查询逻辑。不懂业务黑话的 AI 会怎么写注释和方法名?

java 复制代码
// 糟糕的"翻译腔":AI 只是在做字面翻译
/**
 * 获取商户钱的细节
 */
public List<MerchantFundDetail> getMerchantMoneyDetails(String merchantId) {
    ...
}

这段代码能跑,但放到你们团队的代码库里就像个异类。团队里大家都管这个叫"流水",管扣钱叫"冻扣",结果 AI 给你来了个"钱的细节"。

要解决这个问题,我们需要在 Skills 规则文件里,给 AI 建立一个**"业务术语词典"**,直接把英文类名和业务黑话死死绑定在一起:

markdown 复制代码
## 核心业务术语词典
在生成代码注释、方法名、变量名时,必须严格使用以下业务术语,禁止使用通俗化翻译:
- `MerchantFundDetail` 对应业务词:**资金流水**(禁止翻译为:资金细节、商户钱记录)
- 冻结资金动作:**冻扣**(禁止翻译为:冻结扣除、freeze money)
- `UserAccount` 对应业务词:**会员资产**(禁止翻译为:用户账号、用户钱)
- 分发优惠券动作:**发券**(禁止翻译为:分发折扣票)

当 AI 看到了这个词典,你再让它写同样的逻辑,它就会输出非常地道的"内部代码":

java 复制代码
// 地道的业务代码:完全符合团队语境
/**
 * 根据商户ID查询资金流水
 */
public List<MerchantFundDetail> getMerchantFundFlows(String merchantId) {
    ...
}

将业务缩写与特定名词注入规则

除了业务动作,业务名词的缩写也是重灾区。特别是对于一些特定行业的系统,AI 很难猜到两个字母的缩写到底代表什么。

比如在物流系统中,TO 可能不是 To(去往),而是 Transfer Order(转运单);DO 不是 Data Object,而是 Delivery Order(配送单)。

如果你不告诉 AI,它可能会在方法里写出 getTo() 这种让人摸不着头脑,甚至和 Java 关键字 to 混淆的代码。

在规则文件中,我们需要明确缩写的全称和适用范围:

markdown 复制代码
## 业务缩写与专有名词规范
- `TO`:仅代表 Transfer Order(转运单),不要作为介词 to 的缩写使用。
- `DO`:在 `com.example.logistics.domain` 包下,仅代表 Delivery Order(配送单)。
- `COD`:Cash On Delivery(货到付款),在枚举或常量中必须大写。
- 禁止使用拼音缩写作为变量名(如禁止出现 `kch` 代表库存号)。

规定枚举类和常量类的定义与使用场景

业务"黑话"在代码中最具体的载体,其实就是枚举常量

新手用 AI 时,AI 特别喜欢在业务逻辑里直接写死 123,或者直接写 if (status.equals("SUCCESS"))。这种写法一旦业务状态发生变化,修改起来就是灾难。

我们需要在规则里强制 AI 使用枚举,并规定它怎么写枚举:

markdown 复制代码
## 枚举与常量使用铁律
1. **禁止魔法值**:业务代码中绝对不允许出现表示状态的数字(如 0, 1, 2)或未经定义的字符串(如 "SUCCESS", "FAIL")。
2. **枚举统一定义位置**:所有业务枚举必须放在 `com.example.common.enums` 包下。
3. **枚举规范模板**:枚举类必须包含 `code`(存入数据库的 int 或 String)、`desc`(给前端看的中文描述)两个属性,并提供根据 code 获取枚举的静态方法。

我们可以直接在规则文件里给 AI 贴一个**"满分试卷"**(样板代码),AI 的模仿能力极强,看到模板它就会照着抄:

java 复制代码
// 【规则文件中的样板代码展示】
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * 订单状态枚举
 */
@Getter
@AllArgsConstructor
public enum OrderStatusEnum {
    INIT(10, "待发券"),
    FROZEN(20, "已冻扣"),
    CANCELLED(90, "已作废");
    private final Integer code;
    private final String desc;
    /**
     * 根据 code 获取枚举(AI 必须按照此格式生成其他枚举的查询逻辑)
     */
    public static OrderStatusEnum getByCode(Integer code) {
        return Arrays.stream(values())
                .filter(e -> e.getCode().equals(code))
                .findFirst()
                .orElse(null);
    }
}

有了这个规范和模板,当你要求 AI:"写一个判断订单是否已冻扣的逻辑"时,它再也不会写出 if (order.getStatus() == 20) 这种魔法值代码了,而是会乖乖输出:

java 复制代码
import com.example.common.enums.OrderStatusEnum;
// AI 生成的逻辑:使用了规范的枚举进行判断
if (OrderStatusEnum.FROZEN.getCode().equals(order.getStatus())) {
    // 执行业务逻辑...
}

统一团队黑话,本质上就是把 AI 从一个"懂 Java 语法的机器",驯化为一个"懂你们公司业务的资深员工"。当它写出来的变量名、注释和状态判断跟你身边的同事一模一样时,这才是 AI 辅助编程真正的价值所在。

五、 设定不可逾越的"红线":异常处理与日志规范

如果在代码审查时,只能挑出一个最让人血压升高的点,很多老程序员会把票投给:糟糕的异常处理和日志打印

当你让默认状态的 AI 去"处理一下可能出现的错误"时,它最喜欢干两件事:要么甩给你一个 e.printStackTrace(),要么来一句 System.out.println("出错了:" + e.getMessage())

在生产环境中,前者会把错误信息直接吐到黑洞般的标准输出里,任何日志收集系统都抓不到;后者不仅抓不到,还会因为大量的字符串拼接拖慢系统性能。这就好比家里进了贼,AI 不打 110 报警,而是自己躲在角落里写日记。

我们必须在 Skills 规则文件中,为异常和日志画上不可逾越的"红线"。

告别 e.printStackTrace():统一日志框架与占位符用法

首先要做的就是"禁用老古董",并确立唯一的发声渠道。在 Java 生态里,这几乎是毫无争议的标准答案:Slf4j 配合 Logback。

但仅仅告诉 AI "用 Slf4j"是不够的,AI 在拼接日志时依然会犯错。我们需要在规则中明确规定日志的书写语法

markdown 复制代码
## 日志规范红线
1. **绝对禁令**:严禁出现 `e.printStackTrace()`、`System.out.println()`、`System.err.println()`。
2. **日志门面**:统一使用 Lombok 的 `@Slf4j` 注解注入日志对象,禁止手动声明 `LoggerFactory.getLogger()`。
3. **占位符法则**:日志输出必须使用 `{}` 作为占位符,**绝对禁止**使用字符串拼接(`+`)。
   - ❌ 错误:`log.info("处理用户:" + userId + "失败,原因:" + msg);`
   - ✅ 正确:`log.info("处理用户:{}失败,原因:{}", userId, msg);`
4. **异常堆栈打印**:在 log.error 中打印异常时,必须把异常对象作为最后一个参数传入,不要在 message 里解析异常。
   - ❌ 错误:`log.error("出错:" + e.getMessage());` (丢失了完整堆栈!)
   - ✅ 正确:`log.error("处理用户:{}发生异常", userId, e);`

有了这条红线,当你让 AI 补全一段捕获异常的代码时,它就能输出非常标准的生产级代码:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderService {
    public void processOrder(Long orderId) {
        try {
            // 业务逻辑...
        } catch (Exception e) {
            // 完美符合规范:使用 @Slf4j,使用 {} 占位符,保留完整异常堆栈
            log.error("处理订单:{}时发生系统异常", orderId, e);
        }
    }
}

异常分类哲学:业务异常与系统异常的严格区分

AI 处理异常时另一个大坑是"一锅炖":不管是因为用户没填参数,还是因为数据库连不上,统统抛出一个 RuntimeException 或者直接返回 null。这会导致上层的全局异常拦截器无法区分错误类型,前端也没法给用户展示友好的提示。

我们需要在规则里给 AI 灌输**"异常分类哲学"**,就像医院分诊一样,把感冒发烧和骨折大出血分开处理:

markdown 复制代码
## 异常分类与抛出规范
系统只允许存在两种异常类型,必须严格区分:
1. **业务异常**:
   - 场景:用户输入错误、业务规则不满足(如余额不足、订单已取消)。
   - 处理:必须抛出自定义的业务异常 `com.example.common.exception.BizException`。
   - 特点:这种异常不需要打印 error 日志(因为它是预期内的正常业务拦截),全局拦截器会提取其中的 message 直接返回给前端展示。
2. **系统异常**:
   - 场景:数据库宕机、网络超时、空指针等非预期故障。
   - 处理:不要手动抛出,交由框架向上抛出,由全局异常拦截器捕获。
   - 特点:必须打印 error 级别日志及完整堆栈,用于告警排查;前端统一展示"系统开小差了"。

配合这段文字,我们最好在规则里给出 BizException 的实际样例,让 AI 知道该怎么调用:

java 复制代码
// 【规则文件中的样板代码展示】
import lombok.Getter;
@Getter
public class BizException extends RuntimeException {
    private final Integer code;
    public BizException(String message) {
        super(message);
        this.code = 400; // 默认业务错误码
    }
    public BizException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

现在,你让 AI 写一段"扣减库存"的逻辑,它的表现会变得极其专业:

java 复制代码
import com.example.common.exception.BizException;
public void deductStock(Long skuId, Integer deductCount) {
    Integer currentStock = mapper.getStock(skuId);
    
    if (currentStock == null) {
        // 系统异常:没查到数据,属于非预期故障,向上抛出,由全局拦截器打印error日志
        throw new NullPointerException("SKU数据不存在");
    }
    
    if (currentStock < deductCount) {
        // 业务异常:库存不足,属于预期内的业务规则拦截,只抛 BizException,不打 error 日志
        throw new BizException("库存不足,当前库存:" + currentStock);
    }
    
    mapper.updateStock(skuId, deductCount);
}

校验规则的统一:何时用 @Valid,何时手动抛异常

在接口入参阶段,AI 经常陷入纠结:有时它会在 Controller 里写一堆 if (name == null),有时它又在 Service 层试图用 @NotNull 去校验业务字段。

我们必须帮 AI 理清"防线"的概念:

markdown 复制代码
## 参数校验防线规范
1. **第一道防线(Controller 层 - 格式校验)**:
   - 仅用于校验基础格式:非空、长度、邮箱正则等。
   - 必须使用 `@Valid` 配合 JSR 303 注解(如 `@NotBlank`, `@Size`)。
   - 禁止在 Controller 里写 `if-else` 判断请求参数格式。
2. **第二道防线(Service 层 - 业务校验)**:
   - 用于校验业务规则:查数据库判断唯一性、判断账户状态、判断金额是否足够等。
   - 必须使用上面定义的 `throw new BizException(...)`。
   - 禁止在 Service 层使用 `@Valid` 去做业务规则校验。

通过这三条红线的约束(日志怎么打、异常怎么分、校验在哪做),你相当于给 AI 配备了一套标准的"警报系统"。它写出来的代码将不再是一个漏风的破房子,而是一座有着严密安防体系的堡垒。

六、 摆脱说教,直接上"满分试卷":注入标杆代码示例

在前面的五个章节里,我们给 AI 立了规矩、画了架构图、定了业务黑话。这相当于给新入职的程序员发了一本厚厚的《员工手册》。

但现实情况是,绝大多数人拿到员工手册后是不会从头读到尾的。当遇到具体任务时,他们最快的学习方式是什么?------看老员工是怎么写代码的。

AI 也是一样。虽然它能理解长篇大论的 Markdown 文本,但模型底层最擅长的是模式匹配 。你在规则里写了"请使用 Lombok 和 Slf4j",它可能转头就忘;但如果你直接甩给它一段完美符合规范的代码,它会像照葫芦画瓢一样,精准地复刻出每一个细节。

在 Skills 规则文件中,这一招被称为注入标杆代码

千言万语不如一段真实代码

很多开发者在写规则时,喜欢用否定句:"不要用 java.util.Date"、"不要在 Controller 里写业务逻辑"、"不要手写 Getter"。这种"说教式"的规则有两个致命弱点:

  1. 消耗 Token 注意力:AI 在生成代码时,注意力权重是分散的,它可能在生成到一半时,已经忘记了二十行前那个"不要"。
  2. 没有给出明确出路 :你告诉它"不要手写 Getter",但没告诉它"你要用 @Data",它可能就会换成另一种奇怪的写法。
    相反,如果你直接把一段"满分试卷"拍在它面前,告诉它"以后就照着这个样子写",效果是摧枯拉朽的。在 AI 的眼里,这段代码就是最高权重的"黄金模板"。

如何在规则文件中引用项目中的"模范类"

你不需要把项目里几万行代码全塞进规则文件里(那样会超出上下文限制且浪费算力),你只需要摘取最具代表性的**"代码片段"**。

在引用时,有一个极其关键的技巧:必须包含完整的包名导入

如果不写 import,AI 就只能靠猜。它看到 @GetMapping,可能会给你导入一个老版本的包;看到 @NotNull,可能会导入 javax.validation 而不是你们项目用的 jakarta.validation。完整的 import 列表,就是在替 AI 锁定它背后的类库版本。

你可以这样在规则文件里引入模范类:

markdown 复制代码
## 代码标杆模板
在生成任何 Controller、Service 及其相关对象时,必须严格模仿以下代码的风格、注解使用和导入规范。
这是本项目最标准的"满分试卷",违背以下风格的代码将被拒绝。

标杆 Java 代码展示

下面这段代码,浓缩了我们前五章讲过的所有核心要点(技术栈边界、分层架构、业务黑话、日志异常红线)。你可以直接把这段代码原封不动地贴进你的 .cursorrulesCLAUDE.md 中。

java 复制代码
// ---------------------------------------------------------
// 标杆 1:表现层
// ---------------------------------------------------------
package com.example.mall.controller;
import com.example.mall.common.result.Result; // 统一响应封装
import com.example.mall.dto.req.OrderCreateReq; // 请求入参 DTO
import com.example.mall.dto.resp.OrderDetailResp; // 响应出参 DTO
import com.example.mall.service.OrderService;
import com.example.mall.converter.OrderConverter; // MapStruct 转换器
import jakarta.validation.Valid; // 划定技术栈:Jakarta 命名空间
import lombok.RequiredArgsConstructor; // 划定技术栈:使用 Lombok
import lombok.extern.slf4j.Slf4j; // 划定技术栈:使用 Slf4j
import org.springframework.web.bind.annotation.*;
// 规范体现:使用 @Slf4j,不手动声明 Logger;使用 @RequiredArgsConstructor,不使用 @Autowired
@Slf4j
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
    // 规范体现:单向依赖,Controller 只能注入 Service,绝对不能注入 Mapper
    private final OrderService orderService;
    private final OrderConverter orderConverter;
    // 规范体现:参数校验第一道防线,使用 @Valid,不写 if-else 判断格式
    @PostMapping("/create")
    public Result<OrderDetailResp> createOrder(@RequestBody @Valid OrderCreateReq req) {
        log.info("收到创建订单请求, 业务线:{}, 用户:{}", req.getBizLine(), req.getUserId());
        
        // 规范体现:Controller 极度轻量,只做调用和对象转换(DTO 转 DO 隔离)
        OrderDetailResp resp = orderConverter.toResp(orderService.createOrder(req));
        return Result.success(resp);
    }
}
// ---------------------------------------------------------
// 标杆 2:业务逻辑层
// ---------------------------------------------------------
package com.example.mall.service.impl;
import com.example.mall.common.exception.BizException; // 自定义业务异常
import com.example.mall.dto.req.OrderCreateReq;
import com.example.mall.domain.entity.OrderDO; // 领域对象 DO
import com.example.mall.mapper.OrderMapper;
import com.example.mall.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
    private final OrderMapper orderMapper;
    @Override
    // 规范体现:涉及到数据修改,必须明确标注事务传播机制
    @Transactional(rollbackFor = Exception.class)
    public OrderDO createOrder(OrderCreateReq req) {
        // 规范体现:第二道防线,业务规则校验失败,直接抛 BizException,不打 error 日志
        if (req.getItemCount() > 50) {
            throw new BizException("单次购买商品数量不能超过50个");
        }
        try {
            OrderDO orderDO = new OrderDO();
            // 规范体现:使用业务黑话命名,不写直白的翻译
            orderDO.setBizLine(req.getBizLine()); 
            orderDO.setOrderStatus(OrderStatusEnum.INIT.getCode());
            
            orderMapper.insert(orderDO);
            return orderDO;
            
        } catch (Exception e) {
            // 规范体现:系统异常防线,使用 {} 占位符,异常对象放最后保留完整堆栈
            log.error("创建订单数据库异常, 业务线:{}", req.getBizLine(), e);
            throw e; // 继续向上抛出,交由全局拦截器处理
        }
    }
}
// ---------------------------------------------------------
// 标杆 3:对象转换器 ------ 重点提醒 AI
// ---------------------------------------------------------
package com.example.mall.converter;
import com.example.mall.domain.entity.OrderDO;
import com.example.mall.dto.resp.OrderDetailResp;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
// 规范体现:严禁手写 get/set 进行 DTO 与 DO 的互转,必须使用 MapStruct
@Mapper
public interface OrderConverter {
    OrderConverter INSTANCE = Mappers.getMapper(OrderConverter.class);
    
    OrderDetailResp toResp(OrderDO orderDO);
}

为什么这段"满分试卷"威力巨大?

当你把上面这段代码放进 Skills 规则文件后,AI 的行为模式会发生根本性的改变。它不再是在浩如烟海的 GitHub 开源库里随机抽样,而是把这段代码当作了概率分布的锚点

  1. 自动解决包名冲突 :它看到 import jakarta.validation.Valid;,以后全项目都不会再出现 javax
  2. 自动规避禁用语法 :它看到注入用的是 构造器 + final,以后全项目都不会再出现 @Autowired 字段注入。
  3. 自动复制分层习惯 :它看到 Controller 里调了 Service 又调了 Converter,以后它生成代码时,哪怕你不提醒,它也会自觉地把这两层给拼装好。
    写规则文件的终极心法就是:你想要什么样的代码,就亲手写一段最完美的给它看。 把抽象的规范具象化为一段几十行的样板代码,是让 Cursor、Claude Code 等工具彻底"长"成你想要的样子,最快、最暴力、也最有效的手段。
相关推荐
Hector_zh5 分钟前
JiuwenClaw 持久化存储落地:从方案到生产的实践验证
人工智能·ai编程
vistaup1 小时前
claude code 安装 Superpowers(token消耗多但是流程规范化)
ai编程
AskHarries1 小时前
我用 AI 写了一首歌,并把它上传到了 QQ 音乐、酷狗音乐、酷我音乐
openai·ai编程
甲维斯2 小时前
worktree是什么鬼?Codex和Claude双修把我搞晕了!
人工智能·ai编程
DashVector2 小时前
Zvec v0.4.0 正式发布
数据库·嵌入式·ai编程
玄尺2 小时前
【opencode】opencode插件
ai编程
码途漫谈2 小时前
Easy-Vibe高级开发篇阅读笔记(二十)——多平台开发之个人网页与博客开发
人工智能·笔记·ai·开源·ai编程
码途漫谈3 小时前
Easy-Vibe高级开发篇阅读笔记(二十一)——AI能力强化之RAG 与企业级智能客服
人工智能·笔记·ai·开源·ai编程
维诺菌3 小时前
claude code安装
java·开发语言·ai编程·calude
镜花水月linyi3 小时前
GitHub 已开源:民政部官方的国家地名信息库 MCP & Skill 实现
后端·ai编程·mcp