<!-- title: 构建代码复杂度热点扫描Skill -->

## 概述

本教程带你从零构建一个**代码复杂度热点扫描 Skill**（`complexity-scan`）。它能自动度量函数长度、嵌套深度、圈复杂度三项指标，按"重构优先级"对函数排序，并产出可直接评审的清单。学完你将掌握 Claude Code Skill 的完整开发闭环：`SKILL.md` 前置元数据设计、可执行脚本封装、渐进式披露分层、触发描述调优，以及用 `/skills` 与对话实测验证。适合已能跑通 Claude Code、想把重复的代码评审动作沉淀成可复用能力的工程师与训练营学员。

## 前置条件

- 已安装 Claude Code（CLI 或 IDE 集成均可），能正常启动一个会话；
- 本机有 `python3`（脚本用 Python 编写，便于跨语言做启发式度量）；可选 `git`，用于跑实战案例；
- 了解 Markdown 与 YAML 基础语法；
- 手头有一个待分析的项目目录（任意语言均可，C 系语法度量最准，Python/JS 次之）。

---

## 一、先想清楚：Skill 到底是什么

Claude Code 的 Skill 是一个**文件夹形式的能力单元**，根目录放 `SKILL.md`，里面是一段给 Claude 看的指令正文，旁边可以挂脚本和参考文档。它的核心机制有三层：

1. **元数据触发**：Claude 启动时只读每个 Skill 的 `name` + `description`（而非正文），靠语义判断"现在该不该加载这个 Skill"。所以 `description` 是 Skill 的"门面"。
2. **正文即指令**：被触发后，`SKILL.md` 正文进入上下文，告诉 Claude 分几步、调用哪个脚本、按什么格式输出。
3. **按需展开（渐进式披露）**：脚本和参考文件平时不进上下文，只在 Claude 真要执行时才读取或运行——这控制了 token 成本，也是大型 Skill 不"撑爆"上下文的根本原因。

一句话记忆：**描述决定何时用，正文决定怎么做，脚本与参考决定做多深**。

## 二、需求与指标设计：这个 Skill 要解决什么

代码评审里有一类高频却低智的劳动：盯着几百个函数，判断"哪个最该重构"。人脑很容易被噪音淹没，但"哪个函数最复杂"是高度可量化的。我们选三个业界公认、可启发式计算的指标：

| 指标 | 含义 | 启发式算法 | 阈值（参考） |
|---|---|---|---|
| 函数体行数（`loc`） | 单函数长度 | 从签名行到匹配的结束大括号 | > 50 行预警 |
| 最大嵌套深度（`depth`） | 控制 if/for/try 层叠 | 扫描大括号 `{` 入栈、`}` 出栈，记录峰值 | > 4 层预警 |
| 圈复杂度（`ccn`） | 分支决策密度 | 统计 `if/else/for/while/case/&&/\|\|/?/catch` 等关键字 | > 10 预警 |

最终把三个指标归一化成一个**重构优先级分数** `score`，降序输出 Top N，这就是 Skill 的交付物。设计先行，能避免后面脚本"为写而写"。

## 三、创建 Skill 目录与 `SKILL.md`

Skill 可放两个位置：项目级 `.claude/skills/<name>/`（仅当前仓库可用），或用户级 `~/.claude/skills/<name>/`（所有项目共享）。这里用项目级，更贴近"给某个仓库做评审"的场景。

```bash
# 在你的项目根目录执行
mkdir -p .claude/skills/complexity-scan/scripts
mkdir -p .claude/skills/complexity-scan/references
```

目录骨架如下：

```
.claude/skills/complexity-scan/
├── SKILL.md                 # 入口：元数据 + 指令正文
├── scripts/
│   └── scan.py              # 真正干活的度量脚本
└── references/
    └── metrics.md           # 指标定义与阈值（按需加载）
```

接着写 `SKILL.md`。**注意：`description` 的写法直接决定触发准确率**，要写清"做什么 + 何时该用 + 关键词"。

```markdown
---
name: complexity-scan
description: 扫描代码复杂度热点。度量函数行数、嵌套深度、圈复杂度，按重构优先级排序输出 Top 函数清单。当用户想找出最该重构的函数、做代码评审、评估技术债优先级、或提到"圈复杂度/嵌套太深/函数太长/重构哪些函数"时使用。
---

# 代码复杂度热点扫描

## 何时使用
用户需要定位"最该重构的函数"，或代码评审/技术债盘点时。

## 执行步骤
1. 确认目标目录与语言。默认扫描当前工作目录的 `src/`，语言自动识别。
2. 运行度量脚本：
   `python3 scripts/scan.py <目录> [--top N] [--format json|md]`
3. 读取脚本输出的 Markdown 清单，对 Top 函数逐条给出"为何复杂 + 一句话重构建议"。
4. 若用户追问指标定义，再读取 `references/metrics.md` 解释。

## 输出格式
先给一张 Top N 表（函数、文件:行、loc/depth/ccn、score），再给 2-4 条优先重构建议。

## 注意
- 启发式度量有误差，对宏/模板/字符串里的关键字会高估，结论需结合人工判断。
- 默认只读，不修改任何源码。
```

关键点拆解：

- **`description` 三段式**：能力一句话（扫复杂度热点）+ 交付物（Top 清单）+ 触发场景与关键词（重构/评审/圈复杂度…）。这是 Claude 做"语义匹配"的依据。
- **正文用祈使句编号步骤**，让 Claude 像照着菜谱执行，而不是自由发挥。
- **第三步把"解释指标"显式推迟到追问时**——这正是渐进式披露：`references/metrics.md` 不在主路径上。

## 四、编写度量脚本 `scripts/scan.py`

脚本是 Skill 的"手"，必须做到**单一输入、稳定输出、可被 Claude 直接调用**。我们让它在 `stdout` 直接打印 Markdown，并把同结构数据可选导出 JSON，方便下游。

```python
#!/usr/bin/env python3
# complexity-scan/scripts/scan.py
import os, re, sys, json, argparse

# 决策关键字 -> 圈复杂度贡献
DECISIONS = re.compile(
    r'\b(if|else if|elif|for|while|case|catch|except|and|or)\b'
    r'|\&\&|\|\||\?'
)
# 函数签名启发式（覆盖 C/Java/JS/TS/Go 风格 + Python def）
FUNC_SIG = re.compile(
    r'^\s*'
    r'(?:public|private|protected|static|async|export|export default|func|def|fun)\s+'
    r'.*?\(.*?\)\s*(?:{|:)'
    r'|^\s*def\s+\w+\s*\(.*?\)\s*'
)

def parse_file(path):
    """返回该文件中的函数列表，每项含指标字典。"""
    try:
        with open(path, encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
    except OSError:
        return []

    funcs, i, n = [], 0, len(lines)
    while i < n:
        if FUNC_SIG.match(lines[i]):
            start = i
            depth, peak, ccn, body = 0, 0, 0, 0
            # 定位函数体起始大括号（或 Python 的缩进块）
            while i < n and '{' not in lines[i] and lines[i].strip() != '':
                i += 1
            j = i
            while j < n:
                ln = lines[j]
                body += 1
                depth += ln.count('{') - ln.count('}')
                peak = max(peak, depth)
                ccn += len(DECISIONS.findall(ln))
                j += 1
                # 大括号回到 0 或以下表示函数结束
                if depth <= 0 and j > start + 1:
                    break
            # Python 无大括号：用后续缩进收尾的简单兜底
            if body <= 1:
                j = start + 1
                while j < n and (lines[j].startswith('    ') or lines[j].startswith('\t') or lines[j].strip() == ''):
                    body += 1; ccn += len(DECISIONS.findall(lines[j]))
                    j += 1
            loc = max(body, 1)
            depth_v = max(peak, 1)
            ccn_v = ccn + 1  # 基线为 1
            score = loc/50 + depth_v/4 + ccn_v/10
            funcs.append({
                'file': path, 'line': start + 1,
                'loc': loc, 'depth': depth_v, 'ccn': ccn_v,
                'score': round(score, 2),
            })
            i = max(j, i + 1)
        else:
            i += 1
    return funcs

def collect(root, exts):
    out = []
    for dp, _, files in os.walk(root):
        if 'node_modules' in dp or '.git' in dp or 'vendor' in dp:
            continue
        for fn in files:
            if any(fn.endswith(e) for e in exts):
                out += parse_file(os.path.join(dp, fn))
    return out

def main():
    ap = argparse.ArgumentParser(description='代码复杂度热点扫描')
    ap.add_argument('dir', help='目标目录')
    ap.add_argument('--top', type=int, default=15)
    ap.add_argument('--format', choices=['md', 'json'], default='md')
    ap.add_argument('--ext', default='.py,.js,.ts,.java,.go,.c,.cpp,.cs,.rb')
    a = ap.parse_args()

    funcs = collect(a.dir, tuple(a.ext.split(',')))
    funcs.sort(key=lambda x: x['score'], reverse=True)
    top = funcs[:a.top]

    if a.format == 'json':
        print(json.dumps({'total': len(funcs), 'top': top}, ensure_ascii=False, indent=2))
        return

    print(f'## 复杂度热点 Top {len(top)}（共扫描 {len(funcs)} 个函数）\n')
    print('| 函数定位 | 行数 | 嵌套深度 | 圈复杂度 | 优先级 |')
    print('|---|---:|---:|---:|---:|')
    for f in top:
        loc_f = '⚠️' if f['loc'] > 50 else ''
        print(f"| {f['file']}:{f['line']} | {f['loc']}{loc_f} | {f['depth']} | {f['ccn']} | {f['score']} |")
    print('\n> 阈值：行数>50 / 嵌套>4 / 圈复杂度>10 视为预警。优先级=三者归一化之和。')

if __name__ == '__main__':
    main()
```

这个脚本体现了"agent 友好脚本"的三条铁律：**纯文本协议**（打印 Markdown 表，Claude 不用解析二进制）、**幂等且只读**（不写源码）、**单一职责**（只度量，不下结论）。下结论交给 Claude，因为它最擅长结合业务语义给"重构建议"。

## 五、渐进式披露：参考文件与分层加载

主路径（触发→扫描→给建议）已经够用，但用户常会追问"圈复杂度到底怎么算的"。把长篇解释放进 `references/metrics.md`，平时不占上下文，追问时再读：

```markdown
# 复杂度指标定义（complexity-scan 参考文档）

## 圈复杂度（Cyclomatic Complexity）
衡量程序独立路径数，公式 CCN = E - N + 2P。本 Skill 用启发式：
CCN ≈ 1 + 决策关键字出现次数（if/for/while/case/catch/&&/||/?: 等）。
- 1–10：简单，低风险
- 11–20：可接受，需测试覆盖
- 21–50：高风险，建议拆分
- >50：极难维护，必须重构

## 嵌套深度
从函数体起算的 `{` 入栈峰值。每多一层，人脑理解成本指数上升。
>4 层即应考虑用早返回（guard clause）或提取子函数压平。

## 行数
纯启发式：签名行到匹配结束大括号。注释/空行也计入，属已知偏差。
```

分层结构带来的好处用一张时序说明最直观：

```mermaid
sequenceDiagram
    participant U as 用户
    participant C as Claude Code
    participant SK as SKILL.md(元数据+正文)
    participant S as scripts/scan.py
    participant R as references/metrics.md

    Note over C,SK: 启动阶段：只读所有 Skill 的 name+description
    U->>C: "帮我看看哪些函数最该重构"
    C->>C: 语义匹配 -> 命中 complexity-scan
    C->>SK: 加载正文（步骤指令）
    C->>S: 运行 scan.py src/ --top 15
    S-->>C: Markdown 热点表
    C->>U: 表格 + 重构建议
    U->>C: "圈复杂度怎么算的？"
    C->>R: 此时才读取 metrics.md
    R-->>C: 指标定义
    C->>U: 解释阈值与算法
```

注意：`scan.py` 和 `metrics.md` 在"给建议"阶段都**没有进上下文**，只有真正需要时才加载。这就是 Skill 控制成本、可无限扩展的根基。

## 六、触发测试与 `description` 调优

Skill 写好不等于好用，必须实测触发。在项目根目录启动 Claude Code，依次输入下面三类话术，观察是否命中：

1. **正面（应命中）**：`找出这个仓库里最该重构的函数`、`做一次复杂度评审`、`圈复杂度太高的函数有哪些`；
2. **边界（可命中）**：`这几个函数越来越难维护了，帮我排个优先级`；
3. **反面（不应命中）**：`帮我写个单元测试`、`这段代码有安全漏洞吗`（测试/安全类 Skill 才该响应）。

用 `/skills` 命令可以看到已注册的 Skill 列表与简述，确认 `complexity-scan` 已被识别。若正面话术没命中，多半是 `description` 没覆盖用户真实用词，常见修法：

- **加同义词**：用户常说"太绕/太深/太长"，把它们写进 description；
- **加动词**：与其写名词"复杂度分析"，不如写动作"找最该重构的函数"；
- **加否定边界**：必要时写"不用于安全审计/性能压测"，减少误触发。

调一轮 description，命中率通常能从六成拉到九成以上。

## 七、实战案例：在真实项目上跑通

准备一个含若干"胖函数"的示例目录 `demo/src/`：

```javascript
// demo/src/cart.js —— 故意写复杂的函数
function calculateTotal(items, coupons, user, region) {
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    if (items[i].active) {
      if (items[i].type === 'digital') {
        if (region === 'EU') { total += items[i].price * 1.2; }
        else { total += items[i].price; }
      } else {
        if (items[i].weight > 10) {
          if (region === 'EU') { total += items[i].price * 1.2 + 15; }
          else { total += items[i].price + 10; }
        } else { total += items[i].price + 5; }
      }
    }
  }
  for (let c = 0; c < coupons.length; c++) {
    if (coupons[c].valid && user.tier === 'vip') {
      if (coupons[c].type === 'percent') { total -= total * coupons[c].value; }
      else { total -= coupons[c].value; }
    }
  }
  return total;
}
```

在 Claude Code 中直接说：

> 用复杂度扫描分析 demo/src，给我 Top 5。

Claude 命中 Skill 后，会运行 `python3 .claude/skills/complexity-scan/scripts/scan.py demo/src --top 5`，得到类似输出：

```markdown
## 复杂度热点 Top 5（共扫描 12 个函数）

| 函数定位 | 行数 | 嵌套深度 | 圈复杂度 | 优先级 |
|---|---:|---:|---:|---:|
| demo/src/cart.js:2 | 26 ⚠️ | 6 ⚠️ | 12 ⚠️ | 4.32 |
| demo/src/checkout.js:14 | 41 ⚠️ | 4 | 9 | 2.62 |
| ... | ... | ... | ... | ... |
```

随后 Claude 依据正文第三步，结合业务语义给出结论：

> **优先重构 `calculateTotal`**：嵌套 6 层、圈复杂度 12，主因是"商品类型 × 区域 × 重量"三维度用嵌套 if 表达。建议：
> 1. 用**策略表**替代 type 分支：把定价规则抽成 `{digital:{EU:..}, physical:{heavy:{EU:..}}}` 的数据结构；
> 2. 把"优惠券"循环抽成独立函数 `applyCoupons(subtotal, coupons, user)`，主函数只做编排；
> 3. 用 `items.filter(i=>i.active)` 早返回，砍掉第一层 `if`。
> 预计重构后嵌套降至 2、圈复杂度降至 4 以内。

这一步体现了 Skill 设计的精髓：**机械度量交给脚本，价值判断交给 Claude**，两者分工，结论既有数据支撑又懂业务。

## 八、常见问题与解决

| 现象 | 原因 | 解决 |
|---|---|---|
| `/skills` 里看不到该 Skill | 目录名与 `name` 不一致，或放错位置 | 目录名、`name` 字段、路径三者保持一致；项目级放 `.claude/skills/` 下 |
| 正面话术不触发 | `description` 缺用户真实用词 | 补同义词/动词，参考第六节 |
| 脚本被调用却报权限 | Skill 默认工具不含 Bash 执行 | 在 `description` 或正文说明需运行 Python；必要时在 `SKILL.md` 加 `allowed-tools` 声明 |
| 函数识别遗漏 | 签名正则不覆盖目标语言风格 | 在 `FUNC_SIG` 补充该语言的修饰符（如 Rust 的 `pub fn`） |
| 圈复杂度偏高 | 字符串/注释里的 `if` 被误计 | 先做词法清洗：剥离字符串字面量与注释再计数 |
| Python 函数体行数为 1 | 无大括号走兜底分支 | 调整兜底逻辑，按缩进块收尾 |

## 九、工作流程全景图

```text
flowchart TD
    A[需求:找出最该重构的函数] --> B[设计指标: loc/depth/ccn + score]
    B --> C[创建 .claude/skills/complexity-scan/]
    C --> D[写 SKILL.md: 元数据触发 + 步骤正文]
    D --> E[写 scripts/scan.py: 只读、纯文本输出]
    E --> F[写 references/metrics.md: 按需加载]
    F --> G[/skills 确认注册]
    G --> H[多话术测触发, 调优 description]
    H --> I[真实项目跑 Top N]
    I --> J[Claude 结合语义给重构建议]
    J --> K{结论达标?}
    K -- 否 --> H
    K -- 是 --> L[Skill 上线可复用]
```

## 十、延伸阅读

- Claude Code 官方 Skills 文档（创建、管理、分享）：https://code.claude.com/docs/en/skills
- Claude 帮助中心《如何创建自定义 Skill》：https://support.claude.com/en/articles/12512198-how-to-create-custom-skills
- SKILL.md 前置元数据全字段解析：https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/
- 渐进式披露与工程团队标准化（2026）：https://anhtu.dev/claude-code-skills-2026-progressive-disclosure-and-how-to-standardise-workflow-for-engineering-teams-1062

## 训练营适用性评估

| 维度 | 评估 |
|---|---|
| 适用训练营类型 | 代码工程实战营、AI 提效训练营、重构/技术债专题营；不建议纯算法营 |
| 学员基础 | 会一种主流语言、能看懂正则与 Python 即可 |
| 建议教学时长 | 讲授 45 分钟 + 动手 90 分钟（含在自己项目上跑通） |
| 可叠加扩展 | 学员可替换指标脚本，派生出"重复代码检测""API 体积监控""测试盲区定位"等同构 Skill |

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skill 创建、管理与分享权威说明
2. [Claude Code Skills 2026 — Progressive Disclosure and How to Standardise Workflow](https://anhtu.dev/claude-code-skills-2026-progressive-disclosure-and-how-to-standardise-workflow-for-engineering-teams-1062) — 三层渐进式披露与 SKILL.md 解剖
3. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — name/description/allowed-tools 等前置字段全解
4. [How to create custom skills — Claude Help Center](https://support.claude.com/en/articles/12512198-how-to-create-custom-skills) — 自定义 Skill 落地步骤与组织知识沉淀
5. [Inside Claude Code Skills: Structure, prompts, invocation](https://mikhail.io/2025/10/claude-code-skills/) — Skill 结构、触发与调用机制深度拆解

> 本文由微信公众号「51学通信」原创，站长爱卫生（微信：gprshome201101）。关注公众号，解锁更多通信与 AI 工程实战干货。

> [!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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
