代码去重技能
加载于:base.md
**目的:**防止语义代码重复和代码膨胀。维护一个功能索引,以便Claude在写新东西之前总是知道已经存在什么。
核心理念
┌─────────────────────────────────────────────────────────────────┐
│ 检查后再写 │
│ ──────────────────────────────────────────────────────────── │
│ AI不复制/粘贴 - 它重新实现。 │
│ 问题不在于重复代码,而在于重复目的。 │
│ │
│ 在编写任何新函数之前: │
│ 1. 检查CODE_INDEX.md以了解现有功能 │
│ 2. 在代码库中搜索类似功能 │
│ 3. 如果可能,扩展现有代码 │
│ 4. 仅在没有合适的东西存在时才创建新的 │
├─────────────────────────────────────────────────────────────────┤
│ 编写后:立即更新索引。 │
│ 定期:运行/audit-duplicates以捕获重叠。 │
└─────────────────────────────────────────────────────────────────┘
代码索引结构
维护项目根目录中的CODE_INDEX.md,按功能而非文件位置组织:
# 代码索引
*上次更新:[时间戳]*
*运行 `/update-code-index` 重新生成*
## 快速参考
| 类别 | 计数 | 位置 |
|----------|-------|----------|
| 日期/时间 | 5个函数 | src/utils/dates.ts |
| 验证 | 8个函数 | src/utils/validate.ts |
| API客户端 | 12个函数 | src/api/*.ts |
| 认证 | 6个函数 | src/auth/*.ts |
---
## 日期/时间操作
| 函数 | 位置 | 作用 | 参数 |
|----------|----------|-----------|--------|
| `formatDate()` | utils/dates.ts:15 | 格式化日期 → "2024年1月15日" | `(date: Date, format?: string)` |
| `formatRelative()` | utils/dates.ts:32 | 格式化日期 → "2天前" | `(date: Date)` |
| `parseDate()` | utils/dates.ts:48 | 解析字符串 → 日期 | `(str: string, format?: string)` |
| `isExpired()` | auth/tokens.ts:22 | 检查时间戳是否已过 | `(timestamp: number)` |
| `addDays()` | utils/dates.ts:61 | 给日期加天数 | `(date: Date, days: number)` |
---
## 验证
| 函数 | 位置 | 作用 | 参数 |
|----------|----------|-----------|--------|
| `isEmail()` | utils/validate.ts:10 | 验证电子邮件格式 | `(email: string)` |
| `isPhone()` | utils/validate.ts:25 | 验证带有国家代码的电话号码 | `(phone: string, country?: string)` |
| `isURL()` | utils/validate.ts:42 | 验证URL格式 | `(url: string)` |
| `isUUID()` | utils/validate.ts:55 | 验证UUID v4 | `(id: string)` |
| `sanitizeHTML()` | utils/sanitize.ts:12 | 从输入中剥离XSS | `(html: string)` |
| `sanitizeSQL()` | utils/sanitize.ts:28 | 转义SQL特殊字符 | `(input: string)` |
---
## 字符串操作
| 函数 | 位置 | 作用 | 参数 |
|----------|----------|-----------|--------|
| `slugify()` | utils/strings.ts:8 | 转换为URL slug | `(str: string)` |
| `truncate()` | utils/strings.ts:20 | 截断并添加省略号 | `(str: string, len: number)` |
| `capitalize()` | utils/strings.ts:32 | 首字母大写 | `(str: string)` |
| `pluralize()` | utils/strings.ts:40 | 正确添加s/es | `(word: string, count: number)` |
---
## API客户端
| 函数 | 位置 | 作用 | 返回 |
|----------|----------|-----------|---------|
| `fetchUser()` | api/users.ts:15 | GET /users/:id | `Promise<User>` |
| `fetchUsers()` | api/users.ts:28 | GET /users带分页 | `Promise<User[]>` |
| `createUser()` | api/users.ts:45 | POST /users | `Promise<User>` |
| `updateUser()` | api/users.ts:62 | PATCH /users/:id | `Promise<User>` |
| `deleteUser()` | api/users.ts:78 | DELETE /users/:id | `Promise<void>` |
---
## 错误处理
| 函数/类 | 位置 | 作用 |
|----------------|----------|-----------|
| `AppError` | utils/errors.ts:5 | 带代码的基础错误类 |
| `ValidationError` | utils/errors.ts:20 | 输入验证失败 |
| `NotFoundError` | utils/errors.ts:32 | 资源未找到 |
| `handleAsync()` | utils/errors.ts:45 | 包装异步路由处理程序 |
| `errorMiddleware()` | middleware/error.ts:10 | Express错误处理程序 |
---
## Hooks (React)
| Hook | 位置 | 作用 |
|------|----------|-----------|
| `useAuth()` | hooks/useAuth.ts | 认证状态+登录/登出 |
| `useUser()` | hooks/useUser.ts | 当前用户数据 |
| `useDebounce()` | hooks/useDebounce.ts | 去抖值变化 |
| `useLocalStorage()` | hooks/useLocalStorage.ts | 持久化状态 |
| `useFetch()` | hooks/useFetch.ts | 数据获取带加载/错误 |
---
## 组件 (React)
| 组件 | 位置 | 作用 |
|-----------|----------|-----------|
| `Button` | components/Button.tsx | 带变体的样式按钮 |
| `Input` | components/Input.tsx | 带验证的表单输入 |
| `Modal` | components/Modal.tsx | 对话框覆盖 |
| `Toast` | components/Toast.tsx | 通知弹出 |
| `Spinner` | components/Spinner.tsx | 加载指示器 |
文件头格式
每个文件都应该有一个摘要头:
TypeScript/JavaScript
/**
* @file 用户认证工具
* @description 处理登录、登出、会话管理和令牌刷新。
*
* 关键导出:
* - login(email, password) - 认证用户,返回令牌
* - logout() - 清除会话和令牌
* - refreshToken() - 获取新的访问令牌
* - validateSession() - 检查会话是否有效
*
* @see src/api/auth.ts 为API端点
* @see src/hooks/useAuth.ts 为React钩子
*/
import { ... } from '...';
Python
"""
用户认证工具。
处理登录、登出、会话管理和令牌刷新。
关键导出:
- login(email, password) - 认证用户,返回令牌
- logout() - 清除会话和令牌
- refresh_token() - 获取新的访问令牌
- validate_session() - 检查会话是否有效
另见:
- src/api/auth.py 为API端点
- src/services/user.py 为用户操作
"""
from typing import ...
函数文档
每个函数都需要一个一句话摘要:
TypeScript
/**
* 将日期格式化为人类可读的相对字符串。
* 示例:"2分钟前", "昨天", "3个月前"
*/
export function formatRelative(date: Date): string {
// ...
}
/**
* 验证电子邮件格式并检查一次性域名。
* 返回true表示有效非一次性电子邮件。
*/
export function isValidEmail(email: string): boolean {
// ...
}
Python
def format_relative(date: datetime) -> str:
"""将日期格式化为人类可读的相对字符串。
示例:"2分钟前", "昨天", "3个月前"
"""
...
def is_valid_email(email: str) -> bool:
"""验证电子邮件格式并检查一次性域名。
返回True表示有效非一次性电子邮件。
"""
...
检查前写流程
创建任何新函数之前
┌─────────────────────────────────────────────────────────────────┐
│ 在编写新代码之前 │
│ ───────────────────────────────────────────────────────────── │
│ │
│ 1. 用简单的英语描述你需要什么 │
│ "我需要将日期格式化为相对时间" │
│ │
│ 2. 检查CODE_INDEX.md │
│ 搜索:日期,时间,格式化,相对 │
│ → 找到:formatRelative()在utils/dates.ts │
│ │
│ 3. 评估现有代码是否可行 │
│ - 它是否满足我的需求?→ 使用它 │
│ - 接近但不完全?→ 扩展它 │
│ - 没有合适的?→ 创建新的,更新索引 │
│ │
│ 4. 如果扩展,检查破坏性变更 │
│ - 添加可选参数,不要改变现有行为 │
│ - 为新功能更新测试 │
└─────────────────────────────────────────────────────────────────┘
决策树
需要新功能
│
▼
检查CODE_INDEX.md是否有类似的
│
├─► 找到完全匹配 ──────► 使用它
│
├─► 找到类似的 ──────────► 可以扩展吗?
│ │
│ ┌──────────────┴──────────────┐
│ ▼ ▼
│ 是:扩展 否:创建新的
│ (添加参数) (更新索引)
│
└─► 没找到 ──────────► 创建新的(更新索引)
常见重复模式
模式1:实用函数重新实现
❌ 坏: 创建validateEmail()当isEmail()存在时
// 不要:这已经存在为isEmail()
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
✅ 好: 首先检查索引,使用现有的
import { isEmail } from '@/utils/validate';
if (isEmail(userInput)) { ... }
模式2:略有不同的版本
❌ 坏: 多个日期格式化程序略有变化
// 在文件A
function formatDate(d: Date) { return d.toLocaleDateString(); }
// 在文件B
function displayDate(d: Date) { return d.toLocaleDateString('en-US'); }
// 在文件C
function showDate(d: Date) { return d.toLocaleDateString('en-US', { month: 'short' }); }
✅ 好: 一个函数带选项
// utils/dates.ts
function formatDate(d: Date, options?: { locale?: string; format?: 'short' | 'long' }) {
const locale = options?.locale ?? 'en-US';
const formatOpts = options?.format === 'short'
? { month: 'short', day: 'numeric' }
: { month: 'long', day: 'numeric', year: 'numeric' };
return d.toLocaleDateString(locale, formatOpts);
}
模式3:应该提取的内联逻辑
❌ 坏: 相同的验证逻辑分散在多个文件中
// 在signup.ts
if (!email || !email.includes('@') || email.length < 5) { ... }
// 在profile.ts
if (!email || !email.includes('@') || email.length < 5) { ... }
// 在invite.ts
if (!email || !email.includes('@') || email.length < 5) { ... }
✅ 好: 提取一次,到处导入
// utils/validate.ts
export const isEmail = (email: string) =>
email && email.includes('@') && email.length >= 5;
// 其他任何地方
import { isEmail } from '@/utils/validate';
if (!isEmail(email)) { ... }
定期审计
定期运行/audit-duplicates以捕获语义重叠:
审计清单
- [ ] 实用函数:有做类似事情的函数吗?
- [ ] API调用:有多种方式获取相同数据?
- [ ] 验证:分散的内联验证逻辑?
- [ ] 错误处理:不一致的错误模式?
- [ ] 组件:有类似的UI组件可以合并?
- [ ] 钩子:自定义钩子有重叠逻辑?
审计输出格式
## 重复审计 - [日期]
### 🔴 高优先级(合并这些)
1. **日期格式化** - 发现3个类似函数
- `formatDate()`在utils/dates.ts
- `displayDate()`在components/Header.tsx
- `showDate()`在pages/Profile.tsx
- **行动:** 合并到utils/dates.ts
2. **电子邮件验证** - 内联逻辑在5个文件中
- signup.ts:42
- profile.ts:28
- invite.ts:15
- settings.ts:67
- admin.ts:33
- **行动:** 提取到utils/validate.ts
### 🟡 中等优先级(考虑合并)
1. **用户获取** - 2种不同模式
- `fetchUser()`在api/users.ts
- `getUser()`在services/user.ts
- **行动:** 决定一种模式
### 🟢 低优先级(监控)
1. **按钮组件** - 存在3种变体
- 可能是故意为不同用例
- **行动:** 文档记录差异
向量数据库集成(可选)
对于大型代码库(100+文件),添加向量搜索:
使用ChromaDB设置
# scripts/index_codebase.py
import chromadb
from chromadb.utils import embedding_functions
# 初始化
client = chromadb.PersistentClient(path="./.chroma")
ef = embedding_functions.DefaultEmbeddingFunction()
collection = client.get_or_create_collection("code_index", embedding_function=ef)
# 索引一个函数
collection.add(
documents=["将日期格式化为人类可读的相对字符串,如'2天前'"],
metadatas=[{"function": "formatRelative", "file": "utils/dates.ts", "line": 32}],
ids=["formatRelative"]
)
# 写前搜索
results = collection.query(
query_texts=["将日期格式化为相对时间"],
n_results=5
)
# 返回:utils/dates.ts中的formatRelative - 0.92相似度
使用LanceDB(更轻量)
# scripts/index_codebase.py
import lancedb
db = lancedb.connect("./.lancedb")
# 创建表
data = [
{"function": "formatRelative", "file": "utils/dates.ts", "description": "将日期格式化为相对时间"},
{"function": "isEmail", "file": "utils/validate.ts", "description": "验证电子邮件格式"},
]
table = db.create_table("code_index", data)
# 搜索
results = table.search("验证电子邮件地址").limit(5).to_list()
何时使用向量数据库
| 代码库大小 | 建议 |
|---|---|
| < 50文件 | 仅Markdown索引 |
| 50-200文件 | Markdown + 定期审计 |
| 200+文件 | 添加向量数据库 |
| 500+文件 | 向量数据库必备 |
Claude指令
会话开始时
- 如果存在,读取
CODE_INDEX.md - 注意类别和可用的关键函数
- 保持本次会话的上下文
编写新代码前
- 暂停并检查:“这个存在吗?”
- 在CODE_INDEX.md中搜索类似功能
- 如果不确定,搜索代码库:
grep -r "functionName\|similar_term" src/ - 只有确认没有合适的存在时才创建新的
编写新代码后
- 立即更新CODE_INDEX.md
- 如果是新文件,添加文件头
- 添加函数文档字符串
- 提交索引更新与代码
当用户说"添加X功能"
在实现之前,让我检查一下我们是否已经有类似的东西...
[检查CODE_INDEX.md]
发现:`existingFunction()`在utils/file.ts中做类似的事情。
选项:
1. 直接使用现有函数
2. 用新功能扩展它
3. 如果完全不同的用例,创建新的
你更喜欢哪种方法?
快速参考
更新索引命令
/update-code-index
审计命令
/audit-duplicates
文件头模板
/**
* @file [简短描述]
* @description [这个文件做什么]
*
* 关键导出:
* - function1() - [它做什么]
* - function2() - [它做什么]
*/
函数模板
/**
* [它做什么的一句话描述]
*/
export function name(params): ReturnType {
索引条目模板
| `functionName()` | path/file.ts:line | 用简单的英语做什么 | `(params)` |