关于 unsmell
unsmell 是面向开发者的代码整理技能,旨在改善可维护性并保持预期行为不变。它依据范围检查和具体证据,指导局部或全仓库重构。
主要功能
- 01按范围检查代码库
- 02基于证据分析代码异味
- 03保持行为的重构流程
- 04TypeScript 类型安全审查
- 05验证并汇报完成情况
适用场景
- 01重构指定模块或功能
- 02检查代码库的可维护性问题
- 03整理 TypeScript 类型模型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dobroslavradosavljevic/ skills --skill unsmell安装后在 Claude Code 中输入/unsmell确认已加载。
附带文件agents/openai.yaml
详细介绍
Unsmell
在不改变预期行为的前提下提升可维护性。除非用户指定目标,否则应检查整个仓库;如果用户指定了目标,只修改该范围以及维持其正常运行所必需的最少依赖边。
核心规则
- 先阅读仓库指南并了解惯例,再评判代码。
- 除非用户授权,否则应保留公开行为、API、数据格式和兼容性。
- 优先编写清晰、内聚的代码,而不是机械地缩减代码。
- 不要把每一行重复代码、长名称、大文件或抽象缺失都视为缺陷。必须有具体的维护成本。
- 如果现有的本地模式合理,就沿用它。不要引入相互竞争的架构。
- 分阶段进行内聚的重构,确保每次改动便于审查。避免无关的格式变更和缺乏依据的重写。
- 保留无关的用户改动。切勿仅因某段代码的用途不明显就将其删除。
- 只有当依赖带来的价值明显超过其维护成本,且用户允许安装时,才添加依赖。
- 在 TypeScript 中,应改进类型模型或缩小值的范围,而不是压制编译器。不要用隐藏的不健全性换取显式的类型错误。
确定范围
编辑前先对请求进行分类:
- **全面 Unsmell:**检查完整的第一方代码库,但排除生成文件、供应商代码、构建产物、缓存、锁文件、快照和迁移文件,除非它们正是问题的来源。
- **定向 Unsmell:**仅检查和编辑指定的文件、目录、模块、功能、代码异味类别或依赖路径。可跟踪目标范围外的引用以了解情况,但只有目标重构确有需要时才修改范围外的内容。
如果边界不明确,且会实质性影响改动范围,请提出一个聚焦的范围确认问题。否则,说明推断出的边界并继续处理。
工作流程
1. 梳理仓库结构
检查项目说明、清单、源码根目录、测试、生成代码标记、模块边界和可用的质量检查命令。了解项目主要语言和框架的惯例。
对于全面 Unsmell,应广泛搜索并优先检查热点。对于定向 Unsmell,应跟踪目标的调用方、导入、导出、测试和契约,同时避免不必要地扩大编辑范围。
2. 找出有证据支持的代码异味
检查以下问题:
- 可能逐渐不一致的重复知识或行为,而非无害的结构相似。
- 过长的函数、过大的模块、职责混杂和内聚性低。
- 过深的条件分支、复杂的控制流、布尔标志控制的行为和隐藏的状态转换。
- 基本类型滥用、牵一发而动全身的修改、功能嫉妒、抽象泄漏和不恰当的耦合。
- 组合能力薄弱:掩盖行为的继承或包装器、职责过多的组件、属性逐层传递,以及与领域逻辑混杂的编排逻辑。
- 已通过引用关系和运行时或静态证据确认的无用、不可达、过时或冗余代码。
- 误导性、不一致、过长或过度缩写的名称。
- 目录层级过深、路径片段冗余和所有权边界不清晰。
- 不一致的错误处理、资源清理、异步控制流和重复验证。
- 有证据表明存在的重复高开销工作、意外的 N+1 行为、不必要的内存分配或可避免的渲染。
- 存在重复、脆弱、与实现细节过度耦合的测试,或重构行为周围缺少的测试。
- 下文所述的 TypeScript 逃生通道和类型模型异味。
记录每个候选问题的位置、具体成本、判断把握度、拟议改动、行为风险和受影响的依赖。不要为了凑数而硬找问题。
TypeScript 标准
在 TypeScript 代码库中,重点查找并解决以下问题:
- 显式或隐式的
any、不安全地处理unknown、as any、as unknown as T等双重断言,以及仅用于强制兼容的类型断言。 - 缺乏依据的非空断言、确定赋值断言、掩盖无效状态的可选链,以及隐藏缺失数据的回退值。
@ts-ignore、缺乏依据的@ts-expect-error、已禁用的类型感知 lint 规则,以及用于掩盖源代码错误的生成声明。- 过于宽泛的类型,例如
object、Function、{}、不受约束的Record<string, unknown>、宽松的字符串标识符,以及扩展到超出有效领域状态的联合类型。 - 重复或矛盾的接口、可从源架构或 API 推导的手写类型,以及可能发生偏差的平行运行时定义和类型定义。
- 可选属性过载、布尔状态组合,以及允许不可能状态的可空模型;在适当情况下,优先使用可辨识联合和显式状态转换。
- 未能带来更强保证,却增加复杂度的泛型参数、条件类型、重载、工具类型和类型级抽象。
- 不正确的变体、将可变数据暴露为只读或反之、未经检查的索引访问、不安全的键迭代,以及丢失的字面量类型。
- 在不可信边界缺少运行时验证。静态类型无法验证网络、存储、环境、用户或已解析的数据。
- 仅类型导入/导出错误、循环类型依赖、过大的 barrel 文件、意外的公共导出,以及泄漏内部实现的模块边界。
- 隐藏拒绝的 Promise 和回调类型、被丢弃的 Promise、混杂的同步/异步契约,或比实际运行时行为更窄的错误类型。
修复根本的契约、数据模型、控制流收窄或边界验证。优先使用类型推导、satisfies、带有真实运行时检查的类型谓词、可辨识联合、穷尽性 never 检查,以及项目已使用的架构库所派生的类型。仅在不变量已得到证明但无法表达给编译器时使用断言;将断言范围保持在最小,并在不变量不明显时记录说明。
在有针对性的清理中,不要盲目启用更严格的编译器选项,因为这会扩大工作范围。在完整的 TypeScript 代码异味清理中,检查 tsconfig 的严格性;仅当产生的错误能在所请求的工作范围和兼容性约束内解决时,才建议或启用更严格的选项。
3. 排定优先级
按以下顺序安排工作:
- 正确性隐患和不安全的生命周期行为。
- 高成本的重复内容和边界问题。
- 经常变更或处于核心位置的代码中的复杂性。
- 组合和内聚性改进。
- 命名、文件组织和局部一致性。
优先选择能够消除根本原因并简化后续变更的改动。跳过价值较低的改动。
4. 分轮重构
每轮:
- 通过测试、契约、调用点和类型,确认当前行为。
- 做出能够解决代码异味的最小连贯结构性改动。
- 更新因移动或重命名而受影响的导入、导出、引用、测试、文档和配置。
- 运行范围最小且相关的现有验证。
- 重新检查结果,确认没有引入意外的间接层、语义变化或新的代码异味。
遵循以下默认原则:
- 仅在共享代码代表一个稳定概念时才提取。与其使用错误的抽象,不如适当保留重复代码。
- 按职责和变更频率拆分文件,而非依据任意的行数限制。
- 围绕明确的输入和输出组合小单元。将编排与领域逻辑分开,并在边界处理副作用。
- 如果有助于提升可读性,使用卫语句或专用辅助函数简化控制流。
- 重命名应以清晰为目的,而非一味追求简短。含义准确的长名称可能优于含糊的短名称。
- 仅当改善导航或归属关系带来的收益足以抵消导入改动时,才移动或重命名文件和文件夹。
- 删除死代码前,先检查动态加载、反射、框架约定、脚本以及外部或公共消费者。
- 保留解释原因、不变量或约束的注释;删除仅复述显而易见代码的注释。
- 在 TypeScript 中,不要通过类型断言、抑制错误、放宽泛型、虚假默认值或扩大返回类型来解决错误。保留或增强有效的类型推断和公共契约。
5. 完成指定范围
对于全面消除代码异味,应重复执行发现和重构,直到第一方代码中不再存在高置信度或中等置信度、可采取行动的代码异味。不要只修复一个典型问题就停止。
对于定向消除代码异味,当指定目标已清理并通过验证时即可停止。不要顺带重构相邻区域。
6. 验证
根据改动范围,使用仓库现有的格式化工具、代码检查工具、类型检查器、测试、构建和架构检查。对于 TypeScript,运行不生成文件的项目类型检查;如果已配置类型感知代码检查,也应使用。先从小范围检查开始;对整个代码库进行重构后,再运行现有检查中范围最广且相关的检查。未经批准,不要安装缺失的工具。
如果无法运行验证,应准确说明未验证的内容及原因。若仍有排除区域、不确定的动态引用或既有检查失败,不要声称代码库已完全清理。
完成报告
总结以下内容:
- 检查范围及排除项。
- 已消除的根本代码异味,以及由此带来的结构改进。
- 重要的重命名、移动、提取、合并和删除。
- 已运行的验证及结果。
- 遗留问题及各自原因:超出范围、置信度较低、需要改变行为、缺少前置条件或改动成本过高。
保持报告简洁。先说明已完成的成果,而不是逐项罗列所有改动。