本教程带你从零构建一个代码复杂度热点扫描 Skill(complexity-scan)。它能自动度量函数长度、嵌套深度、圈复杂度三项指标,按"重构优先级"对函数排序,并产出可直接评审的清单。学完你将掌握 Claude Code Skill 的完整开发闭环:SKILL.md 前置元数据设计、可执行脚本封装、渐进式披露分层、触发描述调优,以及用 /skills 与对话实测验证。适合已能跑通 Claude Code、想把重复的代码评审动作沉淀成可复用能力的工程师与训练营学员。
python3(脚本用 Python 编写,便于跨语言做启发式度量);可选 git,用于跑实战案例;Claude Code 的 Skill 是一个文件夹形式的能力单元,根目录放 SKILL.md,里面是一段给 Claude 看的指令正文,旁边可以挂脚本和参考文档。它的核心机制有三层:
name + description(而非正文),靠语义判断"现在该不该加载这个 Skill"。所以 description 是 Skill 的"门面"。SKILL.md 正文进入上下文,告诉 Claude 分几步、调用哪个脚本、按什么格式输出。一句话记忆:描述决定何时用,正文决定怎么做,脚本与参考决定做多深。
代码评审里有一类高频却低智的劳动:盯着几百个函数,判断"哪个最该重构"。人脑很容易被噪音淹没,但"哪个函数最复杂"是高度可量化的。我们选三个业界公认、可启发式计算的指标:
| 指标 | 含义 | 启发式算法 | 阈值(参考) |
|---|---|---|---|
函数体行数(loc) |
单函数长度 | 从签名行到匹配的结束大括号 | > 50 行预警 |
最大嵌套深度(depth) |
控制 if/for/try 层叠 | 扫描大括号 { 入栈、} 出栈,记录峰值 |
> 4 层预警 |
圈复杂度(ccn) |
分支决策密度 | 统计 if/else/for/while/case/&&/\|\|/?/catch 等关键字 |
> 10 预警 |
最终把三个指标归一化成一个重构优先级分数 score,降序输出 Top N,这就是 Skill 的交付物。设计先行,能避免后面脚本"为写而写"。
SKILL.mdSkill 可放两个位置:项目级 .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 条优先重构建议。
## 注意
- 启发式度量有误差,对宏/模板/字符串里的关键字会高估,结论需结合人工判断。
- 默认只读,不修改任何源码。
关键点拆解:
description 三段式:能力一句话(扫复杂度热点)+ 交付物(Top 清单)+ 触发场景与关键词(重构/评审/圈复杂度…)。这是 Claude 做"语义匹配"的依据。references/metrics.md 不在主路径上。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,依次输入下面三类话术,观察是否命中:
找出这个仓库里最该重构的函数、做一次复杂度评审、圈复杂度太高的函数有哪些;这几个函数越来越难维护了,帮我排个优先级;帮我写个单元测试、这段代码有安全漏洞吗(测试/安全类 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 |
本文由微信公众号「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 等场景全覆盖。
