code-deduplication code-deduplication

防止代码中的语义重复和膨胀,通过维护功能索引来确保在编写新代码前已经了解现有功能,从而避免重复。

后端开发 0 次安装 0 次浏览 更新于 3/5/2026

代码去重技能

加载于: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指令

会话开始时

  1. 如果存在,读取CODE_INDEX.md
  2. 注意类别和可用的关键函数
  3. 保持本次会话的上下文

编写新代码前

  1. 暂停并检查:“这个存在吗?”
  2. 在CODE_INDEX.md中搜索类似功能
  3. 如果不确定,搜索代码库:grep -r "functionName\|similar_term" src/
  4. 只有确认没有合适的存在时才创建新的

编写新代码后

  1. 立即更新CODE_INDEX.md
  2. 如果是新文件,添加文件头
  3. 添加函数文档字符串
  4. 提交索引更新与代码

当用户说"添加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)` |