本教程带你从零创建一个可被 Claude Code 自动发现并调用的自定义 Skill——code-smell-scanner(代码异味扫描)。你会掌握 Skill 的目录结构、SKILL.md frontmatter 写法、渐进披露(Progressive Disclosure)三层拆分、脚本挂载、描述调优与 eval 验证的完整闭环。适用人群:已经能用 Claude Code 完成基本编码任务、想把团队经验沉淀成「可复用、可触发」能力的开发者。
claude 可进入交互界面)很多人第一次想「扩展 Claude Code」时,会在四个原语之间纠结:Skill、Subagent、Slash Command、MCP。选错原语,后续维护成本会很高。判断标准只有三条:
| 你的诉求 | 选什么 | 为什么 |
|---|---|---|
| 把一套判断标准/工作流固化下来,让 Claude 在合适时机自动套用 | Skill | 描述驱动触发,模型按需加载 |
| 给 Claude 一个独立上下文去做某类任务(避免污染主对话) | Subagent | 隔离的 context + 限定工具集 |
想用 /foo 显式快捷触发一段固定提示 |
Slash Command | 显式调用,不依赖语义匹配 |
| 接一个外部系统/数据源(数据库、API、文件系统) | MCP | 标准协议,跨客户端复用 |
代码异味扫描的本质是「一套判断标准」——它需要的是知识(什么算长函数、什么算深嵌套),而不是一个新的进程或外部数据源。所以它天生属于 Skill。这一步想清楚,后面就不会把简单问题做复杂。
写 Skill 最常见的失败模式是「什么都想做」。一个合格的 Skill 应该能用一句话说清边界。我们把 code-smell-scanner 的职责锁定为:
边界清晰,触发描述才写得准,Claude 才不会在不该用时误触发。
个人级 Skill 放在 ~/.claude/skills/,项目级放在 .claude/skills/。我们在项目里建:
.claude/skills/code-smell-scanner/
├── SKILL.md # 入口,必填
├── references/
│ └── smell-catalog.md # 异味详表(渐进披露第二层)
├── scripts/
│ └── scanner.py # 自动扫描器(脚本层)
└── evals/
└── cases.md # 验证用例
SKILL.md 是唯一被「按需加载」的入口。它的开头是 YAML frontmatter,只有两个字段是关键:name 和 description。description 决定了 Skill 能否被正确触发,是整个文件最重要的部分。
frontmatter 硬性规则要记牢:
name:小写字母、数字、连字符,最长 64 字符,必须与目录名一致description:最长 1024 字符,用第三人称写「什么时候该用它」,不要写「我能帮你」这种第一人称SKILL.md 的正文(frontmatter 之外的部分)是「第一层披露」:当 Claude 判定该 Skill 相关时,整份正文会被载入上下文。所以它要精炼、可执行,建议控制在 5000 字符以内——把详尽的参考资料拆到单独文件去,别把入口文件撑爆。
下面是完整的 SKILL.md:
---
name: code-smell-scanner
description: Reviews a single source file for code smells — long functions, deep nesting, overlong lines, excessive parameters, oversized files, and leftover TODO/FIXME comments. Use when the user asks to "check code quality", "find smells", "review this file for maintainability", or before submitting a large PR. Returns located findings with line numbers and concrete refactor suggestions; does not modify code.
---
# Code Smell Scanner
You are a maintainability reviewer. You **diagnose, never auto-edit**.
## Workflow
1. Ask the user for the target file (or infer from context). If a directory is given, ask whether to recurse.
2. Run the bundled scanner to get a deterministic baseline:
`python3 scripts/scanner.py <file>`
3. Read the scanner output, then **augment** it with your own semantic judgement (the scanner only catches syntactic smells; you catch design smells like duplicated logic, misleading names).
4. For each finding, cite the line number and give ONE concrete suggestion.
5. Summarize severity at the end: 🔴 must-fix / 🟡 should-fix / 🟢 nit.
## The 6 smells we track
- **Long function** > 50 lines
- **Deep nesting** > 4 levels of indentation
- **Overlong line** > 120 chars
- **Too many parameters** > 4
- **Oversized file** > 400 lines
- **Leftover markers** TODO / FIXME / XXX
Full definitions and edge cases live in `references/smell-catalog.md` — read it only when a finding needs nuance.
## Output format
Always use this table so output is machine-parseable:
| Severity | Smell | Location | Suggestion |
|---|---|---|---|
| 🔴 | Long function | auth.py:login (88 lines) | extract validateCredentials() |
Do NOT reformat, do NOT add preamble. End with a one-line total: `Total: 3 🔴 / 5 🟡 / 2 🟢`.
注意三个细节:第一,描述里塞进了触发词("check code quality"、"find smells"),这些是 Claude 做语义匹配的锚点;第二,正文明确了「诊断不动手」的边界,防止它越界去改代码;第三,详细的异味定义被推到 references/smell-catalog.md,只在需要时才读——这就是渐进披露的核心。
把不常用、但偶尔需要的「深度知识」放进 references/ 目录。Claude 只在第一层正文明确指向它时,才会去读。这样可以省下大量常驻 token。
references/smell-catalog.md 示例片段:
# Smell Catalog — Detailed Definitions
## Long function (> 50 lines)
Count blank lines and comments as part of the body. **Exception**: long `switch`/`match`
statements with simple arms are acceptable — flag them as 🟢 only.
## Deep nesting (> 4 levels)
Indentation level is counted from the function's own scope. A method inside a class
inside a function starts at level 2 before any control flow.
## Too many parameters (> 4)
Options object / builder pattern collapses multiple params into one. Flag the *effective*
param count, not the literal count.
这条「按需加载」的纪律,是 Skill 区别于「一坨巨大的系统提示」的关键。一个团队如果往 SKILL.md 里堆几万字,反而会让 Claude 触发变钝、上下文变贵。
Skill 的一个高价值模式是:让模型负责判断,让脚本负责确定性计算。代码行数、嵌套深度这些是确定性的,交给脚本算比让模型数 token 更准、更省。我们在 scripts/scanner.py 里实现:
#!/usr/bin/env python3
"""Deterministic code-smell baseline scanner. Language-agnostic-ish."""
import sys, re
THRESHOLDS = {"long_func": 50, "deep_nest": 4, "long_line": 120,
"max_params": 4, "max_file_lines": 400}
def scan(path):
findings = []
lines = open(path, encoding="utf-8", errors="ignore").read().splitlines()
if len(lines) > THRESHOLDS["max_file_lines"]:
findings.append(("🟡", "Oversized file", f"{path} ({len(lines)} lines)",
"split into focused modules"))
# overlong lines + leftover markers
for i, ln in enumerate(lines, 1):
if len(ln) > THRESHOLDS["long_line"]:
findings.append(("🟢", "Overlong line", f"{path}:{i} ({len(ln)} chars)",
"wrap or extract"))
if re.search(r"\b(TODO|FIXME|XXX)\b", ln):
findings.append(("🟡", "Leftover marker", f"{path}:{i}",
"resolve or track in issue tracker"))
# long functions + deep nesting + params (brace-based heuristic)
depth = 0; func_start = None; func_name = None; params = 0
for i, ln in enumerate(lines, 1):
m = re.search(r"def\s+(\w+)\s*\(([^)]*)\)", ln)
if m:
func_name, args = m.group(1), m.group(2)
params = len([a for a in args.split(",") if a.strip()])
if params > THRESHOLDS["max_params"]:
findings.append(("🟡", "Too many params",
f"{path}:{func_name} ({params})",
"introduce options object"))
func_start = i
prev_depth = depth
depth += ln.count("{") - ln.count("}")
if depth > THRESHOLDS["deep_nest"]:
findings.append(("🟡", "Deep nesting", f"{path}:{i} (level {depth})",
"extract early-return guard"))
if prev_depth > 0 and depth == 0 and func_start:
if i - func_start > THRESHOLDS["long_func"]:
findings.append(("🔴", "Long function",
f"{path}:{func_name} ({i-func_start} lines)",
"extract helper(s)"))
func_start = None
return findings
if __name__ == "__main__":
for sev, smell, loc, sug in scan(sys.argv[1]):
print(f"{sev} | {smell} | {loc} | {sug}")
SKILL.md 里已写过 python3 scripts/scanner.py <file> 的调用方式,Claude 在触发后会自己跑这条命令、读 stdout,再用语义判断补充。预期结果:对一份 80 行的 auth.py,输出类似:
🔴 | Long function | auth.py:login (88 lines) | extract helper(s)
🟡 | Deep nesting | auth.py:42 (level 5) | extract early-return guard
🟡 | Leftover marker | auth.py:67 | resolve or track in issue tracker
常见问题:脚本报 FileNotFoundError —— 因为 Skill 触发后工作目录是项目根,而非 skill 目录。解决:在 SKILL.md 里写明使用相对 skill 目录的调用方式,或在脚本里用 __file__ 解析绝对路径。另一个坑:Python 缩进计数用花括号 {} 只对 C 系语言准;扫描 Python 时把 {} 换成缩进判断。按你的主力语言取舍即可。
description 是 Skill 的「广告位」,写不好会出现两类故障:漏触发(该用时没被调用)和误触发(不该用时被调用)。调优就是在这两端找平衡。
一个常见的烂描述:
description: Helps improve code quality.
问题:太宽泛、没有触发词、没说边界。Claude 在「重构这段代码」时可能触发(其实你想让它直接改),在「检查这个文件质量」时反而可能不触发。
改写后(即上文 SKILL.md 里那版)写法要点:
自检方法:把 description 念给同事听,问「你会猜它处理哪类问题」。如果对方答非所问,就是没写清楚。
Skill 上线前用 eval 跑一遍,量化「触发准不准、输出对不对」。在 evals/cases.md 里准备几组用例:
| 用户输入 | 期望触发 | 期望输出特征 |
|---|---|---|
| 「帮我看看 auth.py 代码质量」 | ✅ 触发 | 含表格、含行号、含总结行 |
| 「把这段代码重构成 async」 | ❌ 不触发 | 直接动手重构 |
| 「review 这个 PR 的可维护性」 | ✅ 触发 | 先跑 scanner |
| 「写一个新模块」 | ❌ 不触发 | 直接写代码 |
跑法:用 /eval 或 skill-creator 自带的评估流程,对每组输入检查触发与输出。重点看两类失败:误触发会让 Claude 在写新功能时莫名其妙跑去扫描;漏触发会让你的 Skill 形同虚设。迭代 description 直到表格全绿。
项目级 Skill 无需安装,把目录提交进仓库,团队 git pull 后即生效。个人级放 ~/.claude/skills/。验证发现情况:进入 Claude Code 输入 / 看是否列出该 skill,或直接说「帮我检查 src/payment.py 的代码异味」——若描述写得好,Claude 会自动触发。
假设团队有个 legacy/billing.py(1200 行、一个函数 200 行、嵌套 6 层、5 个 TODO)。对它说「扫一下 billing.py 的代码异味」:
code-smell-scanner,载入 SKILL.mdpython3 scripts/scanner.py legacy/billing.py,拿到确定性基线(Oversized file 🟡、多处 Long function 🔴、多处 Deep nesting 🟡、5 个 Leftover marker 🟡)calculate_total 里有大段重复的折扣逻辑(脚本抓不到的设计级异味),追加一条 🔴Total: 4 🔴 / 9 🟡 / 1 🟢整个过程:模型负责语义判断、脚本负责确定性计数,各司其职。这就是「Skill 把团队经验沉淀成可复用能力」的落地形态。
flowchart TD
A[用户输入] --> B{Claude 语义匹配}
B -->|不相关| Z[不加载 Skill]
B -->|匹配 description| C[载入 SKILL.md 第一层]
C --> D[执行工作流]
D --> E[运行 scripts/scanner.py]
E --> F[确定性基线:行数/嵌套/参数]
D --> G[模型语义补充:重复/命名/设计]
F --> H{需要细节?}
H -->|是| I[读取 references 第二层]
H -->|否| J[合并发现]
I --> J
G --> J
J --> K[输出表格+严重度总结]
K --> L[/eval 持续验证触发精度/]
/skill-creator 内置技能——用它脚手架化生成新 Skill、自动跑 eval、做方差分析,比手写更省心[!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 等场景全覆盖。
