AI编码提效实战:Skill、Rule与上下文工程

0 阅读16分钟

上周我让 Claude Code 帮我写一个新模块的 ViewModel。它写得很认真,代码也没有语法错误——但用了 LiveData 而不是 StateFlow,继承了 ViewModel() 而不是用 Hilt 注入,网络请求直接在 viewModelScope 里 try-catch 而不是走我们封装好的 Result 链式调用。

代码能跑吗?能。代码能合进主分支吗?必须全改。

这就是 AI 编码助手在真实项目里最大的尴尬:它写的代码和你项目的代码,不像一个人写的

上一篇聊了怎么用 AI 从 PRD 生成高质量 Spec。有了好 Spec,AI 生成的代码"方向"不会错——但"风格"和"规范"会错。这一篇就解决这个问题:怎么让 AI 真正理解你的项目,写出和你团队风格一致的代码。

核心是三件事:Rule 定规范,Skill 封任务,上下文工程控输入

一、Rule:给 AI 一本你团队的编码手册

先说最直接的方案。2026 年主流 AI 编码工具都支持某种形式的"规则文件"——你在项目根目录放一个特定文件,AI 每次生成代码时都会先读它。

三家的方案长这样:

工具规则文件加载方式
Cursor.cursor/rules/*.mdc按 glob 模式匹配文件路径自动加载
Claude CodeCLAUDE.md项目根目录 + 子目录,自动合并
GitHub Copilot.github/copilot-instructions.md单文件全局生效

看起来只是"放个文件"这么简单的事,但写好这个文件的差距是巨大的。

1.1 Rule 的两个层次:团队规范 vs 模块约定

我把 Rule 分成两层。第一层是团队规范——放在项目根目录,对所有代码生效。第二层是模块约定——放在特定目录下,只对该模块的代码生效。

为什么要分层?因为你的 app 模块和 network 模块写法不一样。app 层写 ViewModel 用 Hilt 注入,network 层写拦截器就是纯 OkHttp。如果把所有规则揉在一个文件里,AI 在写网络层代码时也会莫名其妙给你加 @HiltViewModel

来看一个我在实际项目里用的根目录 CLAUDE.md

# CLAUDE.md

## 项目概览
Android 电商 App,minSdk 26,
targetSdk 35,纯 Kotlin。
架构:MVI + Clean Architecture
(Presentation → Domain → Data)

## 编码规范
- 状态管理:StateFlow,禁止 LiveData
- DI:Hilt,所有 ViewModel 用
  @HiltViewModel + @Inject constructor
- 协程:统一走 domain 层
  UseCase.invoke(),
  ViewModel 不直接调 repository
- 错误处理:sealed class AppResult
  不允许裸 try-catch
- 命名:feature 包按功能命名
  (cart/、profile/、search/),
  不按层命名(viewmodel/、repo/)

## 禁止项
- 禁止 GlobalScope
- 禁止 Dispatchers.IO 硬编码
  (用注入的 CoroutineDispatcher)
- 禁止在 Composable 里直接
  调 suspend 函数
- 禁止 !! 操作符

然后在 feature/cart/ 目录下放一个模块级的 CLAUDE.md

# feature/cart/CLAUDE.md

## 购物车模块上下文
- 状态类:CartUiState(在 state/ 下)
- 事件类:CartEvent(sealed interface)
- 本模块使用乐观更新模式:
  UI 先更新,失败后回滚
- 价格计算统一走 PriceCalculator
  工具类,不要在 ViewModel 里
  手写算术逻辑
- 库存校验是异步的,
  加入购物车时不阻塞 UI

这样 Claude Code 在帮你写购物车模块代码时,既知道全局规范(用 StateFlow、用 Hilt),也知道这个模块的特殊约定(乐观更新、PriceCalculator)。两层叠加,生成的代码一致性直接上了一个台阶。

1.2 Cursor Rules 的 glob 匹配:更精准但更碎

Cursor 的方案更灵活一些。它用 .cursor/rules/ 目录下的 .mdc 文件,每个文件头部声明一个 glob 模式,匹配到的文件编辑时自动加载对应规则。

# .cursor/rules/viewmodel.mdc
# globs: **/viewmodel/**/*.kt,
#        **/*ViewModel.kt

所有 ViewModel 必须:
1. 使用 @HiltViewModel 注解
2. 通过 constructor inject 获取
   UseCase,不直接依赖 Repository
3. 暴露 StateFlow<UiState> 给 UI
4. 使用 sealed interface 定义
   UiEvent

ViewModel 模板:
@HiltViewModel
class {Feature}ViewModel
@Inject constructor(
    private val {xxx}UseCase:
        {Xxx}UseCase,
) : ViewModel() {
    private val _uiState =
        MutableStateFlow(
            {Feature}UiState()
        )
    val uiState =
        _uiState.asStateFlow()
}

好处是精准——只有编辑 ViewModel 文件时才加载 ViewModel 规则。坏处也很明显:规则碎片化。一个中型项目可能需要十几个 .mdc 文件(ViewModel、Repository、Composable、UseCase、Test……),维护成本不低。

我个人的判断:如果团队超过5人,用 Cursor Rules 的 glob 模式更好,因为不同人负责不同模块,规则可以各自维护。如果是个人项目或小团队,Claude Code 的分层 CLAUDE.md 够用且更简洁。

1.3 Rule 写作的五个血泪教训

写了大半年 Rule,踩了不少坑,总结几条最痛的:

教训一:Rule 里要写"不要做什么",不只是"要做什么"。我最开始只写了"用 StateFlow",但 AI 还是会在某些场景里偷偷用 LiveData——因为它的训练数据里 LiveData 的示例太多了。加上"禁止 LiveData"之后,违规率从30%降到接近0。禁止项是 Rule 里效果最立竿见影的部分。

教训二:给模板,别给原则。说"代码要简洁"是废话,AI 不知道你的"简洁"标准是什么。直接给一个你觉得好的 ViewModel 模板代码,比写十条原则有效十倍。AI 是模式匹配的,给它模式它就能复现。

教训三:Rule 不是越长越好。我一度把 CLAUDE.md 写到了2000多字。后来发现 AI 生成速度变慢了不说,遵守度也没提高——信息过载。现在控制在 800 字以内,核心就那么几条,精简且明确。

教训四:写 Rule 时预期"AI 会犯什么错"。不是凭空想规则,而是先让 AI 不带 Rule 写几次代码,看它犯什么错,然后针对性地写 Rule。这样写出来的 Rule 每一条都有实际意义。

教训五:Rule 要跟代码一起 Code Review。有人改了架构但没更新 Rule,后果是 AI 按旧架构生成代码,然后代码跑不起来。Rule 应该和代码一样进 PR Review 流程。我们现在的 .cursor/rules/ 改动必须有人 Approve 才能合入。

二、Skill:把重复任务封装成 AI 可执行的技能

Rule 解决的是"AI 写出的代码像你团队的风格"。但还有另一个问题:有些任务你每天都在做,每次都得给 AI 重新描述一遍。

比如"给这个功能加个新的 API 接口"——你每次都得说:先在 ApiService 加接口定义,然后写 Repository 实现,然后写 UseCase 封装,然后在 di 模块绑定,最后写个单元测试。六个步骤,每次都一样,只是接口名和参数不同。

这就是 Skill 要解决的:把一个多步骤的开发任务,封装成一句话就能触发的"技能"

2.1 Skill 的本质:任务模板 + 上下文注入

不同工具对 Skill 的叫法不同——Cursor 叫 Agent Rule + Notepads,Claude Code 叫自定义命令(/commands),Google 的 Agent Skills 是更标准化的定义。但核心思想都是一样的:

Skill = 任务描述 + 步骤序列 + 上下文文件

触发词:"加个新接口"

Step 1: 读取 ApiService.kt,在末尾添加接口定义

Step 2: 在 data/repository/ 下创建 {Feature}RepositoryImpl

Step 3: 在 domain/usecase/ 下创建 {Feature}UseCase

Step 4: 在 di/NetworkModule 中绑定 Repository

Step 5: 生成 UseCase 单元测试

我来给个实际例子。这是我为 Claude Code 写的一个自定义命令,用来在项目里创建一个完整的新功能模块:

# .claude/commands/new-feature.md

创建新功能模块,参数:
- $FEATURE_NAME:功能名(PascalCase)
- $PACKAGE:包路径

步骤:
1. 创建目录结构:
   feature/$FEATURE_NAME/
   ├── ui/
   │   ├── ${FEATURE_NAME}Screen.kt
   │   └── ${FEATURE_NAME}ViewModel.kt
   ├── domain/
   │   └── ${FEATURE_NAME}UseCase.kt
   ├── data/
   │   ├── ${FEATURE_NAME}Repository.kt
   │   └── ${FEATURE_NAME}RepositoryImpl.kt
   └── di/
       └── ${FEATURE_NAME}Module.kt

2. ViewModel 遵循根目录 CLAUDE.md
   中的 MVI 模式
3. Screen 使用 Compose,接收
   UiState 参数,不持有 ViewModel
   引用
4. Repository 接口在 domain/,
   实现在 data/
5. Module 用 @InstallIn(ViewModelComponent)
6. 为 UseCase 生成单元测试

参考文件(AI 会自动读取):
- feature/cart/(作为已有模块参考)
- core/network/ApiService.kt

用的时候一句话:/new-feature Search com.app.search。十秒钟,六个文件全部生成,而且代码风格和项目里其他模块完全一致——因为它参考了你指定的已有模块。

2.2 高频 Skill 推荐:Android 项目的五个必备技能

经过几个月实战,这五个 Skill 的 ROI 最高:

① new-feature:上面讲过了,创建完整功能模块。每次省30-45分钟脚手架时间。

② add-api:新增 API 接口全链路(ApiService → Repository → UseCase → DI 绑定 → 单测)。每个新接口从手写15分钟压缩到2分钟。

③ compose-screen:根据设计稿描述生成 Compose 页面。关键是在 Skill 里指定好你团队的 Design System 组件库路径——让 AI 用你自己的 AppButtonAppTopBar 而不是 Material3 原生组件。

④ migrate-to-compose:把一个 XML layout + Fragment 页面迁移到纯 Compose。这个 Skill 要指定"保留原有业务逻辑,只改 UI 层"——不然 AI 会趁机重构你的 ViewModel,改着改着就改出 Bug 了。

⑤ write-test:给指定类生成单元测试。Skill 里要指定你团队的测试风格(用 Turbine 测 Flow、用 MockK 不用 Mockito、Given-When-Then 命名等)。

三、上下文工程:让 AI 理解 10 万行代码的秘密

好了,现在 AI 有了你的规范(Rule),有了任务模板(Skill)。但还差一个关键要素:它看不到你的整个项目

一个中大型 Android 项目轻松 10-30 万行代码。AI 的上下文窗口再大——Claude 200K tokens、Gemini 1M tokens——也不可能把全部代码塞进去。何况塞进去也没用,信息太多等于噪音。

上下文工程要解决的问题是:在 AI 完成当前任务所需的最小信息集合和上下文窗口容量之间,找到最优平衡点

3.1 上下文的四个层次

我把 AI 编码时的上下文分成四层,从成本最低到最高:

上下文金字塔

L4:全项目索引

成本最高·效果不确定 ↑

L3:相关文件引用

中等成本·效果好 ↑

L2:模块 Rule

低成本·效果显著 ↑

L1:项目 Rule

最低成本·基础保障

L1 项目 Rule(~500 tokens):根目录的 CLAUDE.md / .cursor/rules,每次自动加载。成本几乎为零。

L2 模块 Rule(~300 tokens):子目录的规则文件,按需加载。

L3 相关文件引用(~2000-5000 tokens):这是差距最大的一层。你要告诉 AI "写这个新 ViewModel 之前,先看一下 CartViewModel.kt 的写法"。手动 @ 引用文件,或者在 Skill 里预设参考文件列表。

L4 全项目索引(10000+ tokens):用 Cursor 的 codebase indexing 或 Claude Code 的 codebase_search 工具。让 AI 自己搜索相关代码。效果好坏取决于项目结构和命名规范。

我的经验结论:L3 的投入产出比最高。精心选择3-5个参考文件,比让 AI 漫无目的搜索全项目有效得多。

3.2 实战:一个被上下文决定的代码质量案例

来看一个真实场景。我要给 "我的订单" 页面加一个下拉刷新功能。

不给上下文的结果

// AI 生成——能跑,但不对
class OrderViewModel :
    ViewModel() {

    private val _isRefreshing =
        MutableStateFlow(false)

    fun refresh() {
        viewModelScope.launch {
            _isRefreshing.value = true
            try {
                val orders =
                    repository.getOrders()
                _orders.value = orders
            } catch (e: Exception) {
                _error.value =
                    e.message
            } finally {
                _isRefreshing.value =
                    false
            }
        }
    }
}

问题一堆:没用 Hilt、裸 try-catch、直接调 repository、没走 UseCase、_isRefreshing 应该合并到 UiState 里。

给了 Rule + 参考文件后的结果

// AI 生成——符合项目规范
@HiltViewModel
class OrderViewModel
@Inject constructor(
    private val
        getOrdersUseCase:
        GetOrdersUseCase,
) : ViewModel() {

    private val _uiState =
        MutableStateFlow(
            OrderUiState()
        )
    val uiState =
        _uiState.asStateFlow()

    fun onEvent(
        event: OrderEvent
    ) {
        when (event) {
            is OrderEvent.Refresh ->
                refresh()
        }
    }

    private fun refresh() {
        viewModelScope.launch {
            _uiState.update {
                it.copy(
                    isRefreshing = true
                )
            }
            getOrdersUseCase()
                .onSuccess { orders ->
                    _uiState.update {
                        it.copy(
                            orders = orders,
                            isRefreshing =
                                false
                        )
                    }
                }
                .onFailure { error ->
                    _uiState.update {
                        it.copy(
                            error =
                                error.toUiError(),
                            isRefreshing =
                                false
                        )
                    }
                }
        }
    }
}

同一个 AI,同一个需求——差别完全来自上下文。后者用了 Hilt 注入、MVI 事件模式、UseCase 链式调用、UiState 统一管理。这才是能直接合入主分支的代码。

3.3 上下文预算管理:别浪费你的 token 额度

上下文不是免费的。每一个塞进去的文件都消耗 token,而 token 既有速度成本(AI 思考变慢),也有金钱成本(API 按 token 计费),还有质量成本(信息过多时 AI 容易"走神")。

我的上下文预算管理策略:

策略一:只引用接口,不引用实现。让 AI 写新的 Repository 实现?给它 Repository 接口文件和一个已有的 Impl 参考就够了。不需要把五个 Impl 都塞进去——它们的模式是一样的,看一个就会了。

策略二:用 .cursorignore / .claudeignore 屏蔽噪音。把 build/、.gradle/、generated/ 等目录排除掉。我还把 proguard-rules.pro 也排除了——AI 看到 ProGuard 规则会"灵感涌现"开始帮你优化混淆配置,完全偏离正题。

# .claudeignore
build/
.gradle/
*.generated.*
**/generated/**
proguard-rules.pro
# 第三方 SDK 的wrapper,
# AI看了反而会照抄它的烂写法
libs/legacy-sdk-wrapper/

策略三:写 "项目地图" 文件。这是我发现的一个非常好用的技巧。在根目录写一个 PROJECT_MAP.md,用极短的篇幅描述项目结构和每个模块的职责:

# PROJECT_MAP.md

## 模块结构
app/          → 壳工程,只有 Application
              和 MainActivity
feature/      → 业务功能模块
  cart/       → 购物车(乐观更新模式)
  profile/    → 个人中心
  search/     → 搜索(ElasticSearch)
  order/      → 订单(带分页)
core/
  network/    → Retrofit + OkHttp 配置
  database/   → Room 数据库
  designsystem/ → Compose 组件库
  common/     → 工具类

## 关键约定
- 每个 feature 独立成模块
- feature 之间通过 Navigation
  解耦,不直接依赖
- core/ 是共享基础设施
- 数据流:Screen → ViewModel
  → UseCase → Repository → API

这个文件只有200 tokens,但它让 AI 对项目有了"鸟瞰图"。AI 不会再把 Repository 放错目录,也不会在 core/ 里创建业务逻辑。

四、三套工具的实战对比

聊了理论,来点硬核对比。我用同一个 Android 项目,分别在 Cursor、Claude Code、GitHub Copilot 三个工具上配置了等价的 Rule 和 Skill,跑了一个月,记录了一些数据。

声明:这是我个人在特定项目上的体验,不是严格基准测试。你的结论可能不同。

维度CursorClaude CodeCopilot
Rule 精细度⭐⭐⭐ glob 精准匹配⭐⭐ 目录层级⭐ 仅全局单文件
Rule 遵守率~85%~90%~70%
Skill 系统Agent Rule + Notepad/commands 自定义命令暂无等价方案
上下文管理@ 引用 + codebase 索引codebase_search + 手动引用自动推断为主
我的选择日常开发主力复杂重构和架构任务快速补全和小改

一个可能有争议的判断:Claude Code 在遵守 Rule 方面做得最好,但它的 IDE 集成不如 Cursor 丝滑。我的解决方案是 Cursor 配 Claude 后端——结合了 Cursor 的交互体验和 Claude 的指令遵循能力。

Copilot 的 Rule 遵守率最低,它的 copilot-instructions.md 经常被"忽略"——特别是当你的指令和它训练数据里的常见模式冲突时。比如你写了"禁止 LiveData",它有时还是会用,因为 LiveData 在训练数据里出现频率太高了。

五、实战演练:从零配置一个 Android 项目的 AI 编码环境

理论讲够了,给一个可以直接抄的实战配置。假设你有一个标准的 Clean Architecture Android 项目。

Step 1:写项目根 Rule(15分钟)

把你项目的核心约定写下来。不用很长——先从那些 AI 最容易犯错的地方开始:

# 最小可用 CLAUDE.md

## 技术栈
Kotlin, Compose, Hilt, Retrofit,
Room, StateFlow, Coroutines

## 架构
Clean Architecture:
UI(Compose) → ViewModel → UseCase
→ Repository → DataSource

## 必须
- ViewModel 用 @HiltViewModel
- 状态用 StateFlow
- 错误处理用 Result<T> 链式调用

## 禁止
- LiveData
- GlobalScope
- Dispatchers.IO 硬编码
- !! 操作符
- 裸 try-catch

就这么多。15 行,不到 200 tokens。但这 15 行能让 AI 生成代码的可用率从 50% 提升到 80% 以上。

Step 2:写项目地图(10分钟)

PROJECT_MAP.md,前面给过模板。关键是写清楚每个目录放什么、模块之间的依赖关系。这个文件你写一次就行,项目结构不变就不用更新。

Step 3:配置 ignore 文件(5分钟)

# .claudeignore / .cursorignore
build/
.gradle/
.idea/
*.generated.*
**/generated/**
local.properties
# 三方 SDK,别让 AI 学坏
libs/

Step 4:创建第一个 Skill(20分钟)

选你最高频的任务。不确定选什么?看看你过去一周给 AI 说得最多的指令是什么——那个就是你的第一个 Skill 候选。

Step 5:迭代(持续)

这是最重要的一步。Rule 和 Skill 不是写一次就完事了。每次 AI 生成的代码不符合你的预期,先想想:是 Rule 没覆盖到,还是上下文没给对?然后更新相应的配置文件。

我团队现在有个习惯:每个 PR 如果涉及到"手动修正了 AI 生成的代码",就在 PR description 里标注一下修正了什么。每周五花30分钟回顾这些标注,更新 Rule。三个月下来,我们的 AI 代码直接可用率从 55% 提升到了 87%。

六、踩坑记录:被 AI 坑过的三次

讲了这么多正面案例,也说几次被坑的经历,让你别走我的弯路。

坑1:AI 参考了错误的文件。有一次我让 Claude Code 写一个新的 Repository,它自动搜索了项目里的相关文件做参考——结果找到的是一个两年前的遗留模块,那个模块还在用 RxJava。AI 按 RxJava 风格写了一版,然后我的所有 Rule 里完全没提 "不要用 RxJava"(因为我以为项目里已经没有了)。教训:遗留代码要么删掉,要么在 ignore 文件里屏蔽。坑2:Skill 步骤太多导致中间出错。我写了一个7步的 new-feature Skill,AI 在执行到第5步时把第2步创建的文件包名写错了——因为上下文太长,它"忘记"了前面的步骤。教训:单个 Skill 最多5步,复杂任务拆成多个 Skill 链式执行。坑3:Rule 互相矛盾。根目录 Rule 写了"用 Coroutines",但数据库模块的 Rule 写了一个 Room 示例代码用了 callback 模式。AI 在写数据库相关代码时就精神分裂了,有时用协程有时用回调。教训:模块 Rule 不能和根 Rule 矛盾,只能在根 Rule 基础上增加细节

本篇核心要点

Rule 分两层:根目录定团队规范,子目录定模块约定——"禁止项"比"推荐项"效果更好

Skill 封装高频任务:把多步骤开发流程变成一句触发词,关键是指定参考文件

上下文工程四层金字塔:项目Rule → 模块Rule → 精选参考文件 → 全局索引,L3 的 ROI 最高

持续迭代是灵魂:每次手动修正 AI 代码都是 Rule 优化的信号

三工具选择:Cursor 交互好 + Claude 遵守强 → Cursor 配 Claude 后端是当前最优解

下一篇我们聊一个更有争议的话题:AI Code Review。AI 能不能当你的 Code Reviewer?它的审查和人类 Reviewer 的审查有什么区别?我在团队里试了一个月 AI 自动 Review——效果有惊喜,但也有意想不到的翻车。敬请期待第4篇《AI Code Review:让每一行代码都有AI审查员》。

—— 全文完 ——