首页 › tutorials › 构建代码复杂度热点扫描Skill

概述

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

前置条件


一、先想清楚: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>/(所有项目共享)。这里用项目级,更贴近"给某个仓库做评审"的场景。

# 在你的项目根目录执行
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 的写法直接决定触发准确率,要写清"做什么 + 何时该用 + 关键词"。

---
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 条优先重构建议。

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

关键点拆解:

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

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

#!/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,平时不占上下文,追问时再读:

# 复杂度指标定义(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)或提取子函数压平。

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

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

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,命中率通常能从六成拉到九成以上。

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

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

// 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,得到类似输出:

## 复杂度热点 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 无大括号走兜底分支 调整兜底逻辑,按缩进块收尾

九、工作流程全景图

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 上线可复用]

十、延伸阅读

训练营适用性评估

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

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方 Skill 创建、管理与分享权威说明
  2. Claude Code Skills 2026 — Progressive Disclosure and How to Standardise Workflow — 三层渐进式披露与 SKILL.md 解剖
  3. Claude Code Skill Frontmatter: Every YAML Option Explained — name/description/allowed-tools 等前置字段全解
  4. How to create custom skills — Claude Help Center — 自定义 Skill 落地步骤与组织知识沉淀
  5. Inside Claude Code Skills: Structure, prompts, invocation — 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学通信知识星球海报-扫码加入