首页 › tutorials › 从零造重复代码检测Skill

Claude Code Skills 开发实战:从零造一个重复代码检测 Skill

概述

本教程面向已经装好 Claude Code、能跑通基本对话与文件操作的读者,目标是用一个真实可运行的项目——重复代码检测 Skill(duplicate-code-detector)——带你完整走完自定义 Skill 的开发全流程。读完你将掌握:SKILL.md 的标准结构与 frontmatter 写法、渐进式披露(Progressive Disclosure)三层加载机制、scripts/ + references/ + assets/ 三类捆绑资源的取舍、动态上下文注入(!command``)、用 skill-creator 做 eval 验证触发准确率,以及把 Skill 装到个人级 / 项目级 / 插件级三个位置的最佳实践。适用人群:想把自己的重复劳动沉淀成"可复用能力"的后端 / 前端 / 全栈工程师,以及希望给团队制定统一工程规范的 Tech Lead。

前置条件


第 0 步:理解 Skill 到底是什么

Skill 的官方定义非常直白:一个文件夹,里面放一份 SKILL.md 加可选的脚本与资源。它和你在 CLAUDE.md 里写一大段"项目记忆"的区别在于——CLAUDE.md 每次会话都全量加载,而 Skill 的正文只在被触发时才进入上下文。

这一点是整个 Skill 体系的灵魂,官方给了它一个正式名字:Progressive Disclosure(渐进式披露)。它分三层:

层级 内容 何时进入上下文 典型体积
Tier 1 name + description 元数据 始终在(系统提示里) ~100 词 / 个
Tier 2 SKILL.md 正文 命中触发时 目标 1500–2000 词
Tier 3 scripts/ references/ assets/ 执行中按需读取 无上限(脚本可不读入直接执行)

也就是说,你装 50 个 Skill,常驻上下文成本只有"50 条 description",几十 KB 而已。这正是 Skill 能让你"既拥有大量能力、又不撑爆上下文窗口"的根本原因。

判断"该不该做成 Skill"的一句话标准:当你反复把同一段指令、检查清单、多步流程粘进对话,或者 CLAUDE.md 里某一节已经从"事实陈述"长成了"操作步骤"时——它就该被抽成 Skill。检测重复代码这件事完美命中:每次重构前你都要跑扫描、读结果、生成报告、给重构建议,流程固定、可脚本化、适合"只在重构时加载"。


第 1 步:规划 Skill 的内容结构

官方的 skill-development 指南给出 6 步法,前两步是"用具体例子理解 Skill"和"规划可复用内容"。我们直接落地。

1.1 列出触发场景(这些会写进 description)

1.2 拆解可复用资源

重复出现的动作 沉淀成什么 为什么
扫描源码、归一化、哈希、聚类 scripts/find_duplicates.py 确定性强、每次都重写、可不读入直接执行(省 token)
窗口大小、忽略目录、语言扩展等调参说明 references/heuristics.md 长、查阅型,不该污染 SKILL.md 正文
报告长什么样 examples/sample-report.md 给 Claude 一个输出格式锚点

1.3 确定目录结构

duplicate-code-detector/
├── SKILL.md                 # 入口(必需)
├── scripts/
│   └── find_duplicates.py   # 核心扫描脚本
├── references/
│   └── heuristics.md        # 调参/CI 集成说明
└── examples/
    └── sample-report.md     # 输出示例

注意:只建你真正需要的子目录。assets/(用于产出的模板、图片、字体)这次用不到,就不要建——官方明确强调"删除用不到的示例目录,只建实际需要的"。


第 2 步:先写脚本(确定性的东西交给代码,而不是模型)

Skill 设计有一条黄金法则:能交给脚本的就别让模型即兴写。脚本省 token、可复用、结果确定。我们先把这个 Skill 最重的部分——扫描脚本——写出来。

在 duplicate-code-detector/scripts/find_duplicates.py 写入:

#!/usr/bin/env python3
"""按归一化哈希定位重复 / 近似重复的代码块。"""
import argparse, hashlib, os, re, json
from collections import defaultdict

DEFAULT_EXT = (".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".java", ".c", ".h", ".cs", ".rb")

def normalize(lines):
    """把变量名/数字/字符串/注释抹平,让"换个名字的复制粘贴"也能命中。"""
    out = []
    strip_str = re.compile(r'"[^"]*"|\'[^\']*\'')
    strip_cmt = re.compile(r'#.*$|//.*$')
    for ln in lines:
        ln = strip_str.sub('"S"', ln)          # 字符串 -> S
        ln = strip_cmt.sub('', ln)             # 去注释
        ln = re.sub(r'\b\d+\b', 'N', ln)       # 数字 -> N
        ln = re.sub(r'\b[A-Za-z_]\w*\b', 'I', ln)  # 标识符 -> I
        ln = re.sub(r'\s+', '', ln)            # 去空白
        if ln:
            out.append(ln)
    return out

def iter_files(root, exts, ignore):
    for dp, dn, fn in os.walk(root):
        dn[:] = [d for d in dn if d not in ignore]
        for f in fn:
            if f.endswith(exts):
                yield os.path.join(dp, f)

def windows(norm, size):
    for i in range(len(norm) - size + 1):
        yield hashlib.md5("\n".join(norm[i:i+size]).encode()).hexdigest(), i + 1

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", default=".")
    ap.add_argument("--window", type=int, default=6, help="每个块多少行")
    ap.add_argument("--min-cluster", type=int, default=2, help="至少重复几处才报告")
    ap.add_argument("--ext", default=",".join(DEFAULT_EXT))
    ap.add_argument("--json", action="store_true")
    a = ap.parse_args()
    exts = tuple(a.ext.split(","))
    ignore = {"node_modules", ".git", "vendor", "dist", "build", ".venv"}
    clusters = defaultdict(list)
    for path in iter_files(a.root, exts, ignore):
        raw = open(path, encoding="utf-8", errors="ignore").read().splitlines()
        norm = normalize(raw)
        seen = set()
        for h, line in windows(norm, a.window):
            if h in seen:           # 同一文件内的滑动重叠只记一次
                continue
            seen.add(h)
            clusters[h].append((path, line))
    dups = {h: l for h, l in clusters.items() if len(l) >= a.min_cluster}
    if a.json:
        print(json.dumps(dups, indent=2)); return
    print(f"# 重复代码报告\n\n扫描目录: `{a.root}` | 窗口: {a.window} 行 | 命中聚类: {len(dups)}\n")
    for rank, (h, locs) in enumerate(sorted(dups.items(), key=lambda x: -len(x[1])), 1):
        print(f"## #{rank} 重复 {len(locs)} 处")
        for p, line in locs:
            print(f"- `{p}:{line}`")
        print()
    print(f"---\n共 {len(dups)} 个重复聚类,建议优先重构重复 ≥3 处的块。")

if __name__ == "__main__":
    main()

操作说明:给脚本可执行权限并自测。

chmod +x duplicate-code-detector/scripts/find_duplicates.py
python3 duplicate-code-detector/scripts/find_duplicates.py --root . --window 6

预期结果:

# 重复代码报告

扫描目录: `.` | 窗口: 6 行 | 命中聚类: 4

## #1 重复 5 处
- ./src/auth/login.py:42
- ./src/auth/signup.py:38
- ./src/api/user.py:120
...

---
共 4 个重复聚类,建议优先重构重复 ≥3 处的块。

常见问题:


第 3 步:写 SKILL.md(最容易踩坑的一步)

SKILL.md 决定 Skill 会不会被触发以及触发后怎么执行。它有两部分:YAML frontmatter(决定触发)+ Markdown 正文(决定执行)。先看完整文件,再逐段拆解。

在 duplicate-code-detector/SKILL.md 写入:

---
name: duplicate-code-detector
description: Detects duplicated and near-duplicated code blocks across a codebase and produces a refactor report. This skill should be used when the user asks to "find duplicate code", "检测重复代码", "代码重复检测", "找出抄来的代码", "refactor duplication", or wants a duplication audit before refactoring.
allowed-tools: Bash, Read, Glob, Grep
---

# 重复代码检测

## 用途
扫描指定目录,按归一化哈希定位"几乎相同"的代码块,输出按重复次数排序的报告,用于重构前锁定最值得抽取公共方法的高频重复。

## 何时使用
- 用户问"哪些代码重复了 / 能不能抽公共方法 / 这段是不是抄过来的"
- 重构、合并、技术债清理前需要一份重复清单

## 执行步骤
1. 运行内置扫描脚本(默认 6 行窗口、跨文件去重):
   ```bash
   python3 scripts/find_duplicates.py --root . --window 6 --min-cluster 2
   ```
2. 对重复 ≥3 处的聚类,优先建议抽取为公共函数 / 组件 / Hook。
3. 调参与多语言支持见 `references/heuristics.md`:缩小窗口抓短片段、放大窗口只留长克隆;`--ext` 限定语言;`--json` 便于接入 CI。

## 报告规范
- 先给总数与最严重的 3 个聚类
- 每个聚类列出 `文件:行号`
- 末尾给一句重构建议;不要直接改代码,除非用户明确要求

## 当前仓库规模
!`find . -type f \( -name '*.py' -o -name '*.js' -o -name '*.ts' \) -not -path '*/node_modules/*' | wc -l`

3.1 frontmatter 四个关键字段

yaml # ❌ 坏:人称错、没触发词、太泛 description: Use this skill when working with code duplication. # ✅ 好:第三人称 + 具体触发短语 description: This skill should be used when the user asks to "find duplicate code", "检测重复代码", ...

3.2 动态上下文注入(一行魔法)

正文最后一行 !`find ... | wc -l` 是 Skill 的杀手锏:Claude Code 在把正文交给模型之前,会先执行反引号里的命令,把输出原位替换进去。于是模型每次看到 SKILL.md 时,里面已经是"当前仓库有 1342 个源文件"这样的实时数字,而不是一句干巴巴的指令。这和官方第一个示例 Skill 用 !`git diff HEAD` 把实时 diff 注入是同一个机制。

3.3 写作风格的硬性要求

官方对正文风格的措辞非常明确:全程用祈使句/动词原形开头,不要用第二人称"你应该"。

# ✅ 推荐
运行扫描脚本,得到聚类结果。
对重复 ≥3 处的聚类,优先抽取为公共函数。

# ❌ 避免
你应该运行扫描脚本。
如果你需要调参,可以看 heuristics.md。

3.4 保持 SKILL.md 苗条

正文目标 1500–2000 词。任何超过这个量的内容(详细调参、CI 集成、多语言 AST 策略)一律下沉到 references/。原则是:信息要么在 SKILL.md,要么在 references,绝不在两边各放一份。


第 4 步:补齐 references 与 examples

在 references/heuristics.md 写调参与集成说明(节选):

# 调参与进阶

## 窗口大小(--window)
- 4–6 行:抓短克隆(重复的工具函数、相似的校验逻辑),噪声偏高
- 8–12 行:只留结构完整的克隆块,适合做"抽取公共方法"的决策
- >12 行:仅命中大段复制粘贴,几乎无误报

## 多语言
默认覆盖 .py/.js/.ts/.go/.java 等。仅扫描 Go:--ext .go
新增语言只需把扩展名加进 --ext,归一化是语言无关的。

## CI 集成
--json 输出机器可读结果,在 CI 里判定:
dups=$(python3 scripts/find_duplicates.py --json --min-cluster 3)
test "$(echo "$dups" | jq 'length')" -lt 5 || exit 1   # 重复块超过 5 个则失败

在 examples/sample-report.md 放一份理想输出,给模型一个格式锚点(内容即第 2 步的"预期结果")。这两类文件默认不进上下文——只有当 SKILL.md 正文里指向它们、且模型判断当前需要时,才会被读入。这就是 Tier 3 的价值。


第 5 步:装到合适的位置

Skill 的存放位置决定"谁能用"。官方定义了四个层级,优先级从高到低:

层级 路径 作用范围
Enterprise 受管设置 全组织所有人
Personal ~/.claude/skills/duplicate-code-detector/SKILL.md 你的所有项目
Project <repo>/.claude/skills/duplicate-code-detector/SKILL.md 仅本项目(可随 git 分享给团队)
Plugin <plugin>/skills/duplicate-code-detector/SKILL.md 启用该插件处

把整个 duplicate-code-detector/ 目录拷到目标位置即可:

# 装成个人级:所有项目可用
cp -r duplicate-code-detector ~/.claude/skills/

# 装成项目级:随仓库分享给同事
cp -r duplicate-code-detector your-repo/.claude/skills/

关键特性:实时变更检测。你修改 ~/.claude/skills/ 或项目 .claude/skills/ 下的 SKILL.md 后,无需重启 Claude Code,当前会话立刻生效——非常适合边改边测。


第 6 步:测试与 eval(用 skill-creator 打磨触发准确率)

6.1 手工两路验证

进入任意仓库,启动 claude,分别用两种方式触发:

# 方式 A:让模型自动触发
> 重构前帮我查一下哪些代码重复了,给个清单

# 方式 B:直接喊命令
> /duplicate-code-detector

两者都应输出一份带文件:行号的报告。若方式 A 没触发——大概率是 description 触发词不够,回第 3 步加短语。

6.2 用 skill-creator 跑 eval(批量验证触发率)

手工测一两条不够,真正可靠的做法是用捆绑的 /skill-creator 跑 eval 基准。流程:

  1. 准备一份测试用例文件,每条包含"用户输入"和"期望是否触发本 Skill": yaml # eval_cases.yaml - input: "帮我把这几段相似的校验逻辑抽成一个函数" expect_trigger: true - input: "解释一下 Python 的 GIL 是什么" expect_trigger: false - input: "find duplicate code in src/" expect_trigger: true - input: "把这个函数改名叫 validateEmail" expect_trigger: false
  2. 在 Claude Code 里运行 /skill-creator,选择"评估现有 Skill" → 指向 duplicate-code-detector 与用例文件。
  3. skill-creator 会逐条模拟触发,输出命中率与误报/漏报清单。
  4. 反复把漏报的真实说法补进 description,把误报场景用更精确的措辞排除——直到命中率稳定在 90% 以上。

常见问题:


实战案例:一次真实的重构前审计

假设你的团队有个 Python + TypeScript 混合仓库 monorepo/,后端 3 个微服务都在各自复制了一套"参数校验 + 错误码返回"的逻辑。目标:重构前生成一份优先级清单。

操作:

cd monorepo
claude
# 在交互里输入:
> /duplicate-code-detector

模型会执行 SKILL.md 的步骤:调用 scripts/find_duplicates.py --root . --window 8 --min-cluster 2(窗口放大到 8,避免短样板噪声),读取 references/heuristics.md 确认参数合理,最后按"报告规范"输出:

## 概览
扫描 1247 个源文件,命中 7 个重复聚类,其中 3 个重复 ≥4 处。

## 优先级 1(重复 6 处,跨 services/auth、services/order、services/pay)
- services/auth/handlers.py:88
- services/order/handlers.py:51
- services/pay/handlers.py:73
- ...
建议:抽取 validate_and_respond(payload, schema) 到 common/http.py,6 处统一替换。

## 优先级 2(重复 4 处,TS 侧用户校验)
- ...

---
建议本周先处理优先级 1,预计减少 ~180 行重复代码。需要我直接动手重构吗?

整个过程从"打开仓库"到"拿到可执行的重构清单"不到 1 分钟,且报告基于脚本的真实扫描结果,而非模型的猜测。把 SKILL.md 放进项目 .claude/skills/ 并提交到 git,全团队任何人 claude 一开就能复用同一套审计标准——这就是 Skill 把"个人经验"变成"团队标准库"的方式。


整体工作流图

flowchart TD
    U([用户提问: 哪些代码重复了?]) --> T1{"Tier 1 元数据\n始终在上下文"}
    T1 --> DEC{description 命中?}
    DEC -- 否 --> N[正常对话回复]
    DEC -- 是 --> T2["Tier 2\n加载 SKILL.md 正文"]
    T2 --> INJ["动态注入\n!find...wc -l\n实时文件数"]
    INJ --> RUN["执行脚本\nscripts/find_duplicates.py"]
    RUN --> NEED{需要调参说明?}
    NEED -- 是 --> T3["Tier 3\n按需读 references/heuristics.md"]
    NEED -- 否 --> RPT[按规范生成报告]
    T3 --> RPT
    RPT --> OUT([输出 文件:行号 + 重构建议])
flowchart LR
    subgraph 开发循环
      A[1 列触发场景] --> B[2 写脚本 find_duplicates.py]
      B --> C[3 写 SKILL.md frontmatter+正文+动态注入]
      C --> D[4 补 references/examples]
      D --> E[5 装到 personal/project 位置]
      E --> F[6 skill-creator 跑 eval]
      F -->|命中率不足| C
      F -->|达标| G([发布])
    end

进阶:从 Skill 升级成 Plugin

当你的重复检测 Skill 长出更多配套能力(比如一个 PreToolUse hook 在每次提交前自动跑检测、一个子代理做深度 AST 克隆分析),就可以把它打包成 Plugin。官方机制:在 Skill 文件夹里加一个 .claude-plugin/plugin.json,它就以 <name>@skills-dir 的形式作为插件加载,可以同时捆绑 hooks、agents、MCP 服务器。Plugin 还支持 plugin-name:skill-name 的命名空间,不会和其他层级的同名 Skill 冲突,适合在组织内分发。

另一个值得记住的能力是 把 Skill 放进子代理执行:对于大型仓库的全面扫描,可以在 SKILL.md 里指示用 Explore 子代理并行收集文件清单,主代理只负责汇总——这是控制上下文膨胀的常用招数。


延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方权威:Skill 结构、frontmatter、动态上下文注入、存放位置、skill-creator eval
  2. anthropics/claude-code · skill-development/SKILL.md — 官方"如何开发 Skill"元指南,含 6 步法、三类捆绑资源、写作风格规范
  3. anthropics/skills (GitHub) — Anthropic 第一方 Skill 仓库(pptx/docx/pdf/xlsx/skill-creator),最佳学习样本
  4. Agent Skills Overview — Claude Platform Docs — Skill 作为跨工具开放标准的定义与机制说明
  5. Claude Code Skills 2026 — Progressive Disclosure (AnhTu.dev) — 三层加载机制深度解读与 CLAUDE.md/Sub-Agents/Hooks/Skills 四件套取舍对比

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入