关于 unslop
unslop 是用于清理代码中冗余内容的 Agent 技能,面向编码 Agent。它提供 19 类代码模式和按差异范围执行的流程,并要求清理不改变程序行为。
主要功能
- 01涵盖 19 类代码冗余模式
- 02坚持清理不改变行为
- 03按差异范围检查
- 04提供验证指引
- 05发现项引用模式编号
适用场景
- 01清理差异中的冗余注释和名称
- 02识别多余封装或防御处理
- 03区分清理与行为变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ howells/ skills --skill unslop安装后在 Claude Code 中输入/unslop确认已加载。
附带文件agents/openai.yaml
详细介绍
Unslop
deslop 在代码领域的对应工具。理念相同,处理对象不同:deslop 去除写作中的特征,unslop 去除代码中的特征。
Agent 编写的代码带有一种标记。不是 Bug——它能通过类型检查,测试也能通过,功能正常。代码里堆满了繁文缛节,因为模型想面面俱到,而不是因为实际需要;代价则落在下一个阅读它的人身上。
每次判断都遵循这条规则:这里的改动不会改变行为。 去掉某个特征后,测试仍能原样通过。如果清理会改变程序的行为,那就不属于此技能的范围——隐藏失败的回退逻辑属于 fail-fast,大到难以在脑中掌握的文件属于 componentize。说明情况,然后继续,不要把行为变更偷偷塞进整理工作中。
判断某个东西是否属于特征之前,先阅读周边代码。局部惯例和抽象的目的都很重要:即使包装函数只有一个调用方,也可能为有意义的操作命名,或保留有用的边界。目录列出的是值得调查的模式,不是自动删除的清单。优先采用能消除已确认噪音的最小改动;不要为了迎合自己的喜好而重写相邻代码。
特征
在发现结果中引用编号,让审阅者能看出所指的是哪种模式,而不只是具体实例。
注释和名称
- 叙述性注释。
// loop over the users、// Phase 1: build the cards。代码已经说明了这些内容。一个步骤所需的说明,只需断言文本或日志行。 - 重复签名内容的文档注释。 "@param userId - the user id"。如果文档没有补充类型中没有的信息,就删掉。如果符号已导出,且确实需要有用的悬停提示,那属于
marginalia,而不是删除。 - 注释掉的备选方案。 留着“以防万一”的另一种做法。Git 里有。
- 过度明确的名称。
userDataObject、handleClickEventHandler、configurationOptionsMap,或在类型名称中重复表达的信息:UserShape、UserInterface。说出名词即可。
繁文缛节
- 只有一个调用方的包装层。 函数、类或模块只在一个地方被调用,而且没有增加任何内容。将其内联。
- 只有一种实现的接口。 为从未出现的第二种实现而编写。
- 没人传入的选项。 默认值是唯一使用值的参数、只有一个有效键的配置对象、只有一种状态的功能开关。
- 为对称而对称。 空的
else、不可能到达的default:、什么都不做的finally、只负责等待的包装层。 - 只保存一个常量的文件。 除非边界本身有意义,否则就把它放到使用它的地方。
防御性噪音
10. 多余的防御性处理。 对不可能缺失的值进行空值检查;编译器已经完成类型收窄后再做运行时 typeof 检查;或只会重新抛出相同错误的 try/catch。先确认这条路径可信;单靠类型注解无法验证运行时输入。保留边界验证,以及能补充有意义的上下文或执行清理的捕获逻辑。会吞掉错误或提供回退值的捕获逻辑,若移除后会改变行为,则归入 fail-fast。
11. 多余的断言。 仅用于消除类型错误的 as any、as unknown as T、本可以通过诚实地收窄类型来避免的非空断言 !,以及先将已知值赋给 unknown 或宽泛记录类型、再断言回原有类型的写法。应使用正确的类型或收窄类型,而不是再添加断言。如果修复类型不匹配需要改变运行时行为,则应报告。
12. 重复实现标准库功能。 已有库函数可用,却又手写了 groupBy、chunk、debounce 或日期格式化函数。只有在确认其对调用方的语义等效后才替换,包括时序、默认值、错误、区域设置和边缘情况;否则应报告。
13. 多余的异步处理。 只有在追踪调用方并保留 Promise 返回值、拒绝行为和调度方式后,才能移除异步语法。没有 await 的 async 函数仍会返回 Promise,并将抛出的异常转换为拒绝;等待普通值仍会挂起后续执行。不能仅因为语法看起来多余,就认定它们确实多余。对不确定的改动应报告,而不是猜测。
输出与遗留问题
面向用户、脚本或监控系统的 CLI 输出、错误消息和日志都是可观察契约。除非另有要求,否则应保持不变。不要为了清理代码而更新快照;无法确认兼容性的改动应予以报告。
- 带装饰的控制台输出。 日志和 CLI 输出中的 Emoji、制表符边框、✅/❌ 状态表演以及横幅标题。
- 祝贺语。 “Successfully completed!”、“All done!”。应报告结果,或者不作说明。
- 复述堆栈的错误消息。 说明发生了什么错误,以及调用方应如何处理;堆栈信息已经包含在内。
- 死代码脚手架。 无人负责的
TODO占位内容、示例代码块,以及任何混入生产路径的 mock、stub 或 fixture。 - 仅断言偶然性 mock 细节的测试。 检查断言要保护什么:调用次数或参数可能是实际契约,例如防止重复写入。应保留有意义的交互检查。对于无法证明任何行为的测试,应予以报告,而不是悄悄删除覆盖,或在请求范围之外开始重写测试。
控制流
- 不必要的嵌套。 新增的
if/else层级若可通过提前返回让代码更易理解,就应避免。只有在执行顺序、副作用、清理和返回值都保持不变时才能扁平化。当嵌套能清晰表达逻辑或符合周边代码风格时,应保留。
步骤
让工作流程与差异范围相称。小型清理应简要说明范围、理由和验证方式;可以不单独列出任务清单。
- 确定范围。 默认以差异为准:分支相对于其合并基准的变更,或工作树中的变更。全面检查整个仓库是另一项工作,需要明确说明,因为这会触及无人要求你修改的代码。
- 选择相关验证。 指明可能发生变化的行为,并使用能够捕捉问题的最精简现有检查。对于仅涉及注释的变更,检查差异和适用的文档检查。记录无关的基线失败,并继续开展安全且独立的工作。保留仓库要求的最终检查;不要启动覆盖整个工作区的逐级检查。
- 逐项对照目录检查差异。 阅读附近的代码以了解局部惯例,然后为每个已确认的实例记录一条发现,每条都注明提示编号和
file:line。阅读时不要改写——边扫描边编辑会打断思路,还会让你开始擅自改进内容。 - 将发现分为移除和转交。 移除项是任何不改变行为的内容。转交项是任何会改变程序行为的内容,并注明负责处理它的技能。绝不要直接处理转交项。
- 执行移除,然后验证受影响的契约。 运行选定的检查,并在相关时检查返回值、副作用和时序。测试通过有助于支持行为等价,但不能证明行为等价。调查结果变化,区分偶发失败和既有失败,并撤销任何会改变行为的移除。
- 报告。 对于小型清理,用一到三句话说明改动内容、是否有事项转交给其他地方,以及检查在改动前后的结果。必要时附上提示编号和文件位置来说明发现;只有在用户要求审计,或差异较大且计数有帮助时,才按提示编号统计数量。
这不是什么
- 不是风格检查。格式应遵循仓库的格式化工具。
- 不是重构。在模块间移动逻辑或提取程序包属于
componentize。 - 不是加固。删除回退机制,让程序明确报错属于
fail-fast,而且这是有意改变行为。 - 不是代码检查工具。机器可以对每个文件检查的规则——没有说明理由的类型断言、参数和返回值中的
unknown、模块模拟、通过Reflect访问属性——都应放在仓库的 lint 配置中;howells-lint会设置@howells/lint,其中包含这些规则。如果 lint 没有加载这些规则,应将其转交处理,而不是逐处手动检查。 - 不是代码审查。正确性缺陷属于另一项工作;如果发现此类问题,应报告并保持原样。