最近我把平时在代码评审里反复做的那套清扫动作,封装成了一个叫Hygiene-sweep的 skill。简单说,它是一份让 AI 助手在接手代码库时能按标准流程执行的“卫生清扫指令包”,核心是六步:扫描、诊断、清理、重构、验证、沉淀。为什么做这件事?因为不同模型、不同 Agent 在“清理代码”这件事上的表现波动实在太大——有时候让它删个死代码,它却顺手把 import 也削了;有时候让它给变量改名,它又不敢动。与其每次手工纠正,不如把它该做的动作、顺序、边界、检查点全部写进一个 skill,让它照着条款执行。这篇文章把整套 skill 的定义、六步拆解、SKILL.md 模板、一次真实清扫过程以及我踩过的坑完整分享出来,适合正在用 AI 辅助写代码、维护老项目,或者想给自己的 Agent 定制专用技能的开发者参考。
1. 为什么要给代码卫生这件事专门做一个 skill
代码卫生不是一个新话题,每个有点追求的程序员都知道要保持整洁。但问题在于,整洁的标准是模糊的。你在评审时说的“这里有点乱”,AI 听到的可能是“把缩进改一下”,也可能是“把所有东西都重写一遍”。这种语义鸿沟,导致让 AI 做代码清扫这件事体验极差,要么动作太轻、扫了个寂寞,要么动作太重、改出了一堆新 bug。
后来我看到 Claude 的 Agent Skills 方案,意识到它本质上就是在给模型“按规程办事”的能力。Skill 不是普通的 Prompt,它是一份可以被加载到上下文里的结构化技能定义,包含元信息、执行步骤、边界条件、检查清单和输出格式。这就像给一个聪明但有点随性的实习生发了一份 SOP,他再怎么自由发挥,大方向也在规程之内。
把代码卫生做成 skill,还有几个实际收益。第一,可复用。同一份 skill 可以用于不同的项目、不同的 AI 工具,不需要每次重新解释需求。第二,可维护。你发现 AI 在某个步骤上容易出错,只需改 skill 里的对应条款,而不是重新调 Prompt。第三,可验证。Skill 内部的检查清单能让 AI 在收尾时自检,减少低级的漏网之鱼。
我最初是在一个历史悠久的 Python 服务端项目上做的实验,那个项目里堆积了很多兼容代码、调试残留和注释掉的旧逻辑。我本来打算手工清理,但粗略估计要花半天。试了一次 Hygiene-sweep 之后,虽然中间也修了几次 skill 定义,但整个清扫流程的框架被完整跑通,后续项目我只要把扫描路径换一下,就能直接复用。这坚定了我把这个 skill 打磨成可分享工具的想法。
2. Hygiene-sweep 的整体设计与六步清扫拆解
2.1 skill 的元信息与触发条件
先看这个 skill 最外层的定义结构。我用的格式兼容 Claude Agent Skills 的 SKILL.md 规范,以 YAML front matter 开头,后面接正文说明。
--- name: hygiene-sweep description: 对指定代码库执行六步清扫:扫描异味、分级诊断、清理冗余、重构规范、回归验证、沉淀基线。适用于代码评审、接手旧项目、提交前自检。 ---这份元信息的作用是让模型在合适的场景下自动识别并调用这个技能。我在 description 里刻意写了“代码评审、接手旧项目、提交前自检”三个触发场景,而不是泛泛地写“清理代码”。因为模型对场景化描述更敏感,触发准确率更高。实践下来,把触发条件写得越具体,误调用的概率越低。
2.2 六步清扫的总览
整个 skill 的核心是六步,我用了一个容易记忆的短语:扫、诊、清、理、验、收。每一步有明确的输入、动作和输出,后一步建立在前一步的结果之上。
| 步骤 | 名称 | 核心动作 | 输出物 |
|---|---|---|---|
| 一 | 扫描 | 遍历代码库,建立代码异味清单 | 气味清单 |
| 二 | 诊断 | 逐个异味分级,判断处理优先级 | 优先级矩阵 |
| 三 | 清理 | 删除死代码和冗余代码 | 清理报告 |
| 四 | 重构 | 改善命名、结构和重复逻辑 | 重构摘要 |
| 五 | 验证 | 编译、测试、静态检查 | 回归结果 |
| 六 | 沉淀 | 把本次问题记录为规则 | 更新后的基线规则 |
有人问,为什么第四步“重构”和第三步“清理”要分开?因为清理是减法,重构是改动。减法的风险相对低,而重构会触碰现有逻辑,稍有失误就可能导致行为变化。如果混在一起做,一旦出现兼容问题,很难定位到底是删除造成的还是重写造成的。分开之后,每一步都有明确的回滚依据。
2.3 第一步:扫描——建立代码气味地图
扫描是整个清扫的地基,扫得全不全,直接影响后面的判断。我要求 AI 在这一步只做一件事:收集信息,不做出任何修改。具体要扫的对象包括:
- 未使用的 import、变量、函数(重点检查.py、.js、.ts 等源码文件)
- 注释掉的代码块
- TODO、FIXME、HACK、XXX 等标记
- 明显重复的逻辑片段
- 遗留的调试输出(print、console.log、debugger)
- 过度嵌套的 if/else 或异常处理
- 魔法数字和硬编码路径
扫描时还要注意捕获“看似使用但实际未使用”的东西。最简单的方法是基于引用关系分析:在 Python 里可以用snakefood或vulture辅助找死代码,在 JS/TS 里可以用 ESLint 的no-unused-vars和ts-prune。但我没有让 AI 完全依赖工具,而是要求它同时读取相关文件的上下文,因为静态分析工具经常会漏掉动态调用(比如通过 getattr 调用的方法)。
这一步的输出物是一份气味清单,每一项要带上文件路径、行号、问题类型和一句简短描述。我在 skill 里给了一个很关键的指令:不要试图在扫描阶段评估价值,只记录事实。因为 AI 太容易在评估时自我说服——“这段代码虽然没用,但可能将来有用”,然后跳过清扫。先把事实摆出来,后续诊断阶段再决定留不留。
2.4 第二步:诊断——给每个异味定级别
诊断阶段要解决的核心问题是“哪些该删,哪些该留”。我引入了三个判断维度:风险度、影响度、紧急度。
风险度指改动这段代码可能导致故障的概率。影响度指这段代码被引用的范围有多广。紧急度指它是否阻碍当前功能开发或测试。AI 需要给每个异味按三维度进行高、中、低评级,然后综合得出一个优先级。
- P0:风险低、影响小、明显冗余——比如未使用的 import、孤立的调试打印,直接清理。
- P1:风险中、影响有限——比如注释掉的代码块、重复逻辑,可以做删除或初步合并。
- P2:风险高或影响大——比如带有历史兼容逻辑的代码、运行时动态调用的函数、正则表达式等,需要先标注,谨慎处理,甚至这次不动。
这个分级特别重要,因为它能抑制 AI 最常见的毛病之一:过度激进地清理。AI 看到一段不太理解的代码时,往往会倾向于删除它“让代码更干净”,但真实项目里很多“奇怪代码”是历史债务,动它要非常小心。我在 skill 里写了一条铁律:任何带有 deprecated、legacy、hotfix、temporary 字样注释的代码,一律默认 P2,除非有测试覆盖,否则严禁一键删除。
诊断阶段的输出是优先级矩阵,格式像一张表格,方便人工审阅。这一步通常不需要 AI 立即动手,而是要把矩阵展示给用户确认。我在实际使用时发现,让人工在这个环节把一次关,后面会顺很多——因为用户对自己项目的业务背景最清楚,能有效纠正 AI 对某些业务保留代码的误判。
2.5 第三步:清理——做减法,而且要安全地做
清理阶段执行「删除」,但必须遵守几条安全边界:
- 每次对文件的修改必须最小化,不允许一个文件同时被多处大改。
- 删除未使用 import 之前,必须在整个项目范围内搜索该符号的所有引用,确认全部为零。
- 删除注释掉的代码块时,保留一行注释说明“此处原功能已由 xx 模块替代”,避免后续维护者一脸茫然。
- 涉及公共接口的删除操作必须直接跳过,只把问题记录成 P2 项目。
为什么要保留一行说明注释?这是我踩过坑之后总结的。有一次 AI 把几大段注释掉的旧解析逻辑删得干干净净,结果两周后另一个同事跑过来问“以前那个 fallback 分支是怎么写的”,我翻遍 git 历史才找出来。所以现在的 skill 里明确要求:对于有明显业务含义的注释代码,删除时在原位置生成一行替代注释,说明历史用途和被替代原因。这会让 diff 在 code review 里看起来更稳妥,也给人留了一条追溯线索。
清理完成后,AI 要输出清理报告,列出每个文件的改动摘要,包括删除了哪些行、什么原因、是否留下了替代注释。这里我还特别要求 AI 报告“被跳过但保留了记录的项目”,从而保证整个过程透明、可审计。
2.6 第四步:重构——处理结构问题不是重写
重构阶段我只允许针对两类问题:命名不合理和明显重复的局部逻辑。命名不合理指的是变量名、函数名起得太随意,比如data2、temp_flag、do_thing之类。重复局部逻辑指的是在同一文件或相邻文件里出现了超过 15 行的相同结构,可以安全抽成辅助函数。
但我绝不允许 AI 在这一步做大范围架构调整。原因很简单:skill 的目标是“卫生清扫”,不是“架构改造”。一旦涉及跨模块提取公共父类、引入设计模式、调整包结构,就应该把建议写进“观察笔记”,而不是当场执行。这个约束很重要,因为 AI 很容易被“让代码优雅一点”的冲动带偏,改着改着就偏离了清扫的初衷。
在具体的重构动作里,我要求 AI 先说明“原逻辑是什么、新逻辑是什么、为什么行为等价”,然后才允许动手。比如把一个嵌套过深的 if 改成提前 return,必须先验证每个 return 分支和原来的 else 分支语义一致。命名重构时,要求全局搜索旧的命名,确认没有遗漏引用。
2.7 第五步:验证——用自动化给清扫兜底
清扫完成不等于万事大吉,验证环节不能省。我要求 AI 在修改后依次执行:
- 运行项目的核心测试套件(pytest、jest、go test 等),确认没有回归。
- 运行 linter 或静态检查,确认没有因为清理引入新的未定义变量或 import 缺失。
- 对于改动到的模块,编译或解释执行一次入口代码,确保没有运行时错误。
- 如果项目有代码格式化工具,比如 black、prettier、gofmt,收尾时执行一次。
在 skill 里我还写了一个特别的操作:对比改动前后的关键函数签名。如果重构改动了任何函数签名,必须列出前后对照,并让用户确认是否涉及外部调用。这是防止“悄悄改了接口,导致调用方崩溃”的关键防线。
验证阶段有一点经常被遗漏:只跑测试,没跑构建。许多项目测试能过,但在打包或编译时因为资源路径、循环引用等问题挂掉。我的 skill 里强制要求:如果项目有构建脚本或打包脚本,验证时必须执行一次,哪怕只是 dry-run。我在一个小型 Node.js 项目里就是这么抓住问题的——删掉了一个看起来没用的工具函数,结果打包时才发现那条代码被动态 import 引用着。
2.8 第六步:沉淀——把经验变成可复用的规则
最后一步是我自己加的,也是我觉得最值钱的一步。每次清扫之后,AI 要把那些反复出现的“误导点”和“危险模式”追加到 skill 的 knowledge 段落里,形成自我更新的经验库。比如某次发现“删除函数前必须在 test 目录里搜索所有匹配方法名”,这个规则就被记录进去了。
沉淀不一定每次都能更新 skill 文件本身,但至少要把结论写进一份输出文档里,比如:
hygiene-sweep-report: project: xxx-service cleaned_files: 23 removed_lines: 891 skipped_p2_items: 4 new_rules_derived: 2这些规则慢慢会积累成你团队专属的“代码卫生宪法”。我现在的 skill 里已经攒了一堆很细的条款,比如“Python 中不要信任 vulture 对getattr动态属性的报错”、“markdown 文档中的代码块也要纳入扫描范围”等等。
3. 把六步封装成可直接复用的 SKILL.md
3.1 文件结构与基本骨架
一个可以运行的 skill,在 Claude 或兼容环境中通常需要具备固定的文件结构。我习惯这样组织:
hygiene-sweep/ ├── SKILL.md └── references/ ├── checklist_scan.md ├── priorities.md └── case_library.mdSKILL.md 是入口,references 里放那些不需要常驻主上下文、但在执行对应步骤时会被加载的详细文档。这样做的好处是,不会让 skill 的主体太长,从而占用过多上下文窗口;AI 用到某一步时再去查对应细节,效率更高。
SKILL.md 的主体部分需要包含:
- 执行前置条件:用户要指定扫描路径、是否启用自动清理、是否跳过测试等。
- 六步流程的逐步说明:每个步骤的执行定义、输入、输出、禁止事项。
- 用户确认点:在哪些环节必须停下来等用户确认,比如诊断分级之后、重构之前。
- 最终输出格式:报告如何展示,建议在哪个地方保存清理日志。
3.2 关键指令写法:如何让 AI 真正按步骤执行
我最初写 skill 时犯过一个典型的错误:把所有说明写成一个长段落,结果模型经常把它们当成参考信息而不是操作指令。后来我改成用命令式的编号步骤和明确的“必须/禁止”语气,效果立刻不一样。
## 执行流程 你必须严格按以下顺序执行,禁止跳步、禁止提前修改任何文件。 ### 第一步:扫描 - 在用户指定的路径下,递归检索所有源码文件。 - 筛选出前三类问题:未使用代码、注释代码、调试残留。 - 禁用规则:本步骤不许执行任何写入操作。 ### 第二步:诊断 - 对每条扫描结果,按 risk/impact/urgency 三个维度打分。 - 将结果分为 P0、P1、P2 三档,并按表格输出。 - 必须暂停,等待用户确认后再进入下一步。这里的“必须”和“禁止”不是随便写的。模型对禁止性指令的遵循率通常高于建议性指令。但要注意,不能过于泛滥,否则 skill 会显得僵硬。我只在关键安全边界上用“必须/禁止”,普通操作可以用“建议/尽量”。
3.3 通过确认点控制 AI 的行为节奏
另一个重要设计思路是设立强制确认点。我总共设置了两个:
- 诊断分级结果输出后,等待用户确认是否按优先级执行清理。
- 清理完成、重构开始前,等待用户确认清理报告和 diff 摘要。
这两个确认点既给了用户掌控感,也在关键时刻兜住了 AI 的过度激进。中间步骤可以全自动执行,模型不会因为在清理和重构之间来回横跳而迷失方向。你如果想让 skill 更自动化,可以把确认点改成“当检测到 file_count 少于 10 或清理规模小于 30 行时,可以跳过确认直接执行”,这个阈值可以根据你自己的舒适度调整。
3.4 references 目录让 skill 更清晰
references 里的文档让主 SKILL.md 保持精简。比如 checklist_scan.md 里可以放一份更细的“扫描正则列表”:
# 常用扫描规则片段 unused_import = r'^import .*$' # 结合 AST 判断是否引用 debug_print = r'^\s*(print|console\.log)\(.*# DEBUG' comment_code = r'^\s*#.*(def |class |if |for |while )'但不是每个 skill 都得有 references。如果你的项目比较小、六步规则一次能写完,那就没必要拆。我之前试着把每一步都拆成单独文件,结果 AI 在加载 reference 时花了额外精力,反而拖慢了执行。所以结构要跟随复杂度走,不是越细越好。
4. 实战记录:用 Hygiene-sweep 清扫一个 Python 服务
为了讲得更具体,我在这里展示一次真实的清扫记录。项目是一个内部数据服务,Python 3.9 + Flask,源码约 8000 行,有一个基本的 pytest 测试集。我交给 AI 的指令很简单:使用 hygiene-sweep skill 扫描 scan_path=./service auto_clean=false。
扫描阶段发现 246 个问题,聚合后主要分成五类:
| 问题类型 | 数量 | 示例 |
|---|---|---|
| 未使用的 import | 57 | import requests但从未使用 |
| 注释掉的代码 | 89 | 一段被注释的旧鉴权逻辑 |
| 调试 print | 12 | print("xxx result:", result) |
| 重复代码片段 | 8 | 三处几乎一样的 header 构造逻辑 |
| 命名不规范 | 34 | flag1、data_2、get_info_again |
诊断阶段经过用户确认,决定 P0 直接清理 87 项,P1 清理 76 项,P2 跳过 83 项。跳过的主要是历史兼容代码和动态调用的辅助函数。清理阶段实际删除了 612 行,留下了 6 条替代注释。重构阶段把三处重复的 header 构造逻辑合并成一个build_common_headers函数,并给几个含义不明的变量改了名。验证阶段跑完 pytest 通过,flake8 通过,构建脚本 dry-run 通过。
整个流程我在旁边盯着,最值得称赞的部分是清理只用了两次迭代。第一次迭代 AI 在删除某个工具函数时漏看了生命周期中的触发器引用,在验证环节被构建脚本抓了出来;它立刻回滚了那处改动,并把这个教训追加到了 skill 经验库。第二次迭代一气呵成,没有再出现同类错误。如果用纯手工来做,这些工作至少需要半天,而且很可能漏掉藏在深层文件里的孤魂代码。
有人可能会问:这样让 AI 清扫,出了 bug 谁负责?我的答案是,责任仍然在工程师。Skill 只是把工程实践流程化,不能替代人的判断。你要做的是通过确认点、验证步骤和 diff 审查,把风险控制在可接受范围。我现在每次清扫完,都会在 review 时重点看 AI 的清理报告,尤其要看它“跳过”的那些项目——被跳过的原因比被删除的内容更值得琢磨。
5. 常见问题与排查技巧实录
5.1 AI 经常跳过不常用的文件怎么办
很多 AI 在扫描时会“偷懒”,只处理它认为相关的目录,比如跳过 test、docs、migrations、vendored 目录。这在某些场景下是优点,但代码卫生清扫中会造成漏网之鱼。我的解决方案是在 skill 里显式列举必须扫描的目录,以及允许排除的目录:
默认扫描范围: - 所有源代码目录(app/, src/, lib/) - 所有测试目录(test/, tests/) - 所有配置文件和脚本(*.py, *.js, *.ts, *.sh) - 文档中的代码块(*.md) 默认排除: - vendor/, node_modules/, dist/, build/, .git/这样做之后,AI 的依据就不只是“阅读习惯”,而是一个明确的扫描矩阵。如果你有特殊目录,也可以以问题参数的形式传入,覆盖默认值。
5.2 清理时误删了看似没用的代码
这是我最常遇到的问题。一个典型场景是:某个函数在仓库里搜不到任何引用,你以为它是死代码,但实际上它被另一个脚本拼接字符串后动态调用,或者在运行时通过importlib.import_module动态加载。我现在的 skill 里有两条硬性规则:
- 删除任何函数或类之前,必须在项目全局搜索“该符号名的字符串形式”,如果出现在字符串或动态 import 中,一律转入 P2。
- 删除任何装饰器、元类或魔法方法时,默认跳过,除非用户明确指定。
这两条规则几乎解决了我之前遇到的所有误删情况。另外,如果项目使用 git,我还会要求 AI 在执行清理前创建一个临时分支,以便随时 revert。
5.3 测试全过了但部署后出问题
测试全通过不等于真的没问题,这个问题很常见。原因往往是测试覆盖不足,尤其缺乏集成测试和端到端测试。在验证阶段,我的 skill 会额外检查代码覆盖率报告:
pytest --cov=service --cov-report=term-missing如果发现清扫涉及的模块覆盖率低于 50%,就自动停止修改并要求用户决定是否继续。这种“覆盖率闸门”虽然简单,但很有效。有一次清扫中,AI 准备重构某个覆盖率只有 1% 的模块,就被这道闸门拦了下来。后来人工检查发现那个模块是大量网络请求兼容层,确实不适合在这种清扫任务里触碰。
5.4 Skill 本身不符合你的项目语言怎么办
代码卫生六步清扫是语言无关的,但我在步骤里引用了 pytest、ESLint 等具体工具。如果你用的是 Go、Rust 或 Java,需要把工具名替换掉,比如:
| 语言 | 测试工具 | 静态检查工具 | 格式化工具 |
|---|---|---|---|
| Go | go test | go vet | gofmt |
| Rust | cargo test | clippy | rustfmt |
| Java | mvn test | Checkstyle | google-java-format |
| Python | pytest | flake8 | black |
| JavaScript | jest | eslint | prettier |
我建议在 skill 的扫描阶段额外增加一步“识别项目技术栈并选择合适的验证工具”,让 AI 自己适配,而不是把工具名写死在仓库里。你在首次使用 skill 时,可以让 AI 先输出一份它认为合适的工具组合,经过你确认后再开始清扫,这样最稳妥。
5.5 大仓库扫描时上下文爆炸怎么办
大型代码库动辄几万行,如果让 AI 一边扫描一边把全部内容塞进上下文,很快就超过模型窗口限制。我的处理方法是要求 AI 采取分批次精扫策略:
- 先运行一次快速命令(比如
git ls-files、find . -name '*.py'),生成文件清单。 - 对清单里的每个文件,使用可以“精确检索符号引用”的工具输出关键命中点,而不是把整个文件原文放进上下文。
- 对于候选问题文件,再读取相关片段进行详细确认。
这样能大幅减小上下文占用。但对于极大型项目,我的另一个建议是:不要一次性扫全库,而是按模块清扫。比如首次只清扫service/和utils/,后续再扫其他目录。卫生清扫本来就是一个渐进过程,一次完成所有事情对维护者也是负担。
5.6 AI 输出的报告太啰嗦,影响可读性
我一度发现 AI 的清扫报告会写得特别长,把每一步的思考过程都倒出来,读起来费劲。虽然细节丰富,但对代码 review 来说效率太低了。后来我在 skill 里加了一个明确的“输出精简规则”:只输出结构化表格和必要结论,禁止输出推理过程。如果你也遇到类似问题,可以在最后加一条:
报告格式要求: - 只用表格和列表呈现结果。 - 每个文件的改动不超过三行摘要。 - 禁止解释“为什么我这样做”之类的元讨论。 - 最末尾单独列出需要人工复核的 P2 项目。这算是一个很朴素的解决方式,但在实际协作里价值巨大。人机协作的关键,就是让 AI 的产出形态贴合人的阅读习惯,而不是反过来。
6. 设计这个 skill 时踩过的一些关键坑
写 Hygiene-sweep 的过程里,我至少重构了三遍。第一遍和第二遍的版本都走不通,现在回头看,核心问题出在我没有把“清理边界”讲清楚。初版允许 AI 做“代码清理”,它就真的把所有看起来不符合最佳实践的代码都改了,结果 diff 惨不忍睹。第二版加上了分级和确认点,但确认点太多,每扫一个小问题都要停下来确认,效率极低。第三版才逐渐平衡好“自动执行”和“人工确认”的关系。
还有一个心理层面的点:让 AI 承认“这个不能动”比让它承认“这个可以删”更难。模型天然有种倾向,想在任务中展现执行力,所以你对它说“清理代码”,它会努力把更多代码标记为待清理。因此我在 skill 里对 P2 项目的处理特别强调了“保留并记录”是一种价值输出。后来报告里的 P2 列表经常比实际改动列表更有用,因为它帮人工找到那些被遗忘的兼容逻辑和技术债。
另外我建议你把 skill 当代码一样维护。每次使用后,检查它有没有产生新的边界需求,把这些增量反馈补进去。我的 skill 现在已经从最开始的 400 字长成了 2000 多字,很多条款都是在真实清扫中发现的特殊情况。比如“当用户项目是 Flask 时,默认不要删除与路由绑定的函数”、“当文件头部有 eslint-disable 注释时,必须询问原因后再处理”。这些都是运行经验换来的。
最后聊一个小技巧:你可以把 Hygiene-sweep 和其他技能串联使用。比如先让一个 agent 用这个 skill 清扫代码,再基于清扫后的结果生成 API 文档或测试用例,效果会比直接在脏代码上生成好很多。我自己现在已经把这一套流程接到了日常的代码评审里,每次提交 PR 前都会让 AI 快速跑一遍扫描和诊断,只在紧急情况下才做完整清理。这让人工评审的负担轻了非常多,也让“代码卫生”从一个模糊的概念变成每天都能低成本执行的具体动作。