关于 code-quality
用于提升代码可读性、可维护性和务实性能的 skill,涵盖计划、编写、审查和简化模式。它结合通用规则与特定技术栈指南;可仅报告发现,也可在简化模式中应用选定的机械重构。
主要功能
- 01支持计划、编写、审查和简化模式
- 02提供语言无关的质量规则
- 03包含特定技术栈指南
- 04计划和审查模式仅报告发现
- 05简化模式应用选定重构
适用场景
- 01审查拟议的实现计划
- 02编写代码时落实可读性与可维护性原则
- 03审计代码或审查拉取请求
- 04测试通过后简化已实现的代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mthines/ agent-skills --skill code-quality安装后在 Claude Code 中输入/code-quality确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/citations.mdquick-reference.mdrules/abstraction.mdapi-design.mdarchitecture.mdcognitive-complexity.mdcollaboration.mdcomments.mdcontrol-flow.mdcorrectness.mderror-handling.mdfunctions.mdmaintainability.mdnaming.mdoutput-contract.mdperformance.mdplan-mode.mdprocedure.mdrefactor-recipes.mdreview-checklist.mdsimplify-mode.mdtestability.mdtradeoffs.mdrules/stacks/README.mdrules/stacks/nextjs/endpoints.mdrules/stacks/react/autosave.mdcomponents.mddata-fetching.mdoffline-sync.md详细介绍
代码质量技能
编写易于审查、理解和修改的代码。优先减轻下一位开发者的心智负担,再考虑机器性能,因为可读的代码更容易修改、调试,也更值得信任;而大多数性能提升都来自算法选择和性能分析,而非微优化。
此技能关注以下三个质量维度,按优先级排列:
- 可读性——读者首次阅读时就能从头到尾理解代码。
- 可维护性——为一个概念添加下一个变体,只需修改一个文件、一处内容;复用现有工具,而非重新发明;每个概念都有唯一的权威归属。
- 务实的性能——默认采用算法层面的优化,只有性能分析器指出需要时才进行微优化。
此技能适用于四种模式:
- 计划模式——在编写任何代码之前,通过
Skill("code-quality", "plan")调用。读取plan.md(或拟议方案)和现有代码库,检查计划的结构是否遵循现有模式,并规避可预见的设计阶段风险(过早使用并行映射、缺少带品牌类型的原始值、错误路径缺少类型、参数蔓延、邻近项不匹配)。仅返回发现的问题,不修改代码。由autonomous-workflow的第 1 阶段使用。参见rules/plan-mode.md。 - 编写模式——编写新代码时(例如 TDD 的 GREEN 阶段、新功能)。在编写过程中应用原则,确保第一个版本就达到标准。未传入参数时的默认模式。
- 审查模式——代码编写完成后,通过
Skill("code-quality", "code")或Skill("code-quality", "review")调用。进行重构、审查 PR,或收到“清理一下”的请求时使用。根据规则诊断问题并提出针对性的修改建议。仅返回发现的问题,不修改代码。 - 简化模式——功能实现且测试通过后,通过
Skill("code-quality", "simplify")调用。先执行审查,然后在Skill("confidence", "code") ≥ 90 %且通过范围限定的快速检查后,逐项应用机械式重构(rules/refactor-recipes.md中的 M 类方案);失败时回滚。判断类方案(J 类)仅作为建议。参见rules/simplify-mode.md。变体:simplify aggressive还会自动应用影响中等的机械式方案;simplify dry-run只报告,不写入;simplify <path>将范围限定为指定路径,而非差异内容。
根据 $ARGUMENTS 的第一个词元判断模式:plan → 计划模式;code 或 review → 审查模式;simplify → 简化模式;其他任何内容(包括无参数)→ 编写模式。旧约定“用户说了 review 或 audit”仍会在临时调用时路由到审查模式。
核心主张
认知复杂度——代码有多难理解——是衡量长期可维护性的最佳单一指标。SonarSource 的研究表明,圈复杂度(路径计数)忽略了真正让人头疼的因素:嵌套、线性流程被打断,以及相互叠加的决策。因此,下列规则关注的是认知负担,而非理论复杂度分数。完整引用文献(SonarSource、Clean Code、Knuth、Alexis King、Gary Bernhardt)及每条规则的研究依据见 references/citations.md。
拿不准时,可以用这个启发式问题:读者能否不回头查看,一遍从头到尾理解这个函数? 如果可以,就提交。如果他们必须滚动查看、跳转,或在脑中记住一串条件,就应该修正。
可维护性方面对应的问题是:如果我要为这个概念添加下一个显而易见的变体,需要修改多少个文件?如果漏掉一处,类型系统能否发现? 在一个文件中实现并获得完整类型覆盖是很好的做法;而在四个文件中手动同步四张映射表则属于散弹式修改——应先修正结构,再添加变体。参见 rules/maintainability.md。
快速参考(将这些模式加载到工作记忆中)
| 坏味道 | 重构为 | 原因 |
|---|---|---|
嵌套的 if(3 层以上) | 守卫子句 + 提前返回 | 每增加一层缩进,心智负担就会增加;扁平结构更容易理解 |
| 函数过长(50 行以上,承担多项职责) | 按意图提取,并按“做什么”而不是“怎么做”命名 | 一个函数 = 一个变更理由 |
含义晦涩的名称(d、tmp、data) | 领域名词(priceDifference、pendingOrders) | 名称就是文档 |
| 布尔参数 | 两个有明确名称的函数或一个枚举 | send(true, false, true) 难以理解 |
| 解释“做了什么”的注释 | 重命名或提取函数 | 注释会过时;名称会随代码一起重构 |
| 解释“为什么”的注释(针对不明显的约束) | 保留 | 这是唯一值得保留的注释 |
| 针对不可能状态的防御性检查 | 删除 | 信任调用方;在边界处验证 |
| 标志/选项参数簇(4 个以上参数) | 对象参数或构建器 | 工作记忆大约只能容纳 4 个信息块 |
return/throw 之后的 else | 删除 else | 线性流程优于分支 |
| 魔法数字/字符串 | 命名常量 | 未来的你不会记得 7 代表什么 |
针对同一联合类型(以 Status 为键)的并行映射(LABELS、COLORS、ICONS) | 一个 Record<Status, { label, color, icon }> | 添加一个变体只需修改一处,而非 N 处 |
| 重新实现已有的辅助函数 | 先搜索;使用现有函数 | 两种实现会逐渐偏离;只修复其中一个副本里的 bug |
根据某个值分派的 if/else if 链 | 查找表或单一事实来源记录 | 数据结构优于控制流;易于扩展 |
在多个文件中重复定义同一常量(MAX_RETRIES、状态字符串) | 提取到一个共享模块中 | 一个概念,一个归属处 |
| 添加一个新变体需要修改 4 个以上文件 | 添加变体之前先整合 | 每增加一个变体,散弹式修改都会进一步加剧 |
为同一形状分别定义 type User = {...} 和 userSchema = z.object({...}) | 使用一个 schema;type User = z.infer<typeof UserSchema> | 两份声明会逐渐偏离;一份声明则不会 |
| 在调用栈深处重复验证已解析的值 | 在边界处解析一次;内部信任类型 | 验证是边界问题,而不是每次调用都要处理的问题 |
| 为了“整洁”而把每个嵌套对象都拆成独立的子 schema | 除非子形状会被复用或有自己的边界,否则保持扁平 | 过早拆分;过度设计 |
| 函数混合编排语句与底层细节 | 按抽象层级提取(R16) | 读者只需关注自己关心的层级 |
| 用运行时检查强制规定“这两个字段不能同时设置” | 使用判别联合(R15) | 由编译器强制执行不变量;运行时检查也随之消失 |
Email、UserId、OrderId 使用原始 string 类型 | 通过 schema 为类型添加品牌(R11) | 混用 ID 会成为编译错误 |
使用 any 或无正当理由的类型断言来绕过类型检查器 | 使用 schema 解析、收窄后的 unknown,或 // because: 注释(R17) | 类型 bug 最常见的形式就是无正当理由的 any |
函数直接调用 Date.now()/Math.random() | 注入时钟/随机数生成器(R9) | 纯函数可测试;非纯函数容易导致测试不稳定 |
函数因“未找到”而抛错,或返回哨兵值(-1、"") | 将函数设计为全函数:返回 null 或 Result<T, E>(R10) | 可以穷尽测试全函数 |
每个错误都抛出相同的 Error,再从消息中解析信息 | 使用判别联合 AppError(R12) | 结构化字段优于解析字符串 |
| 导入模块会触发副作用 | 使用工厂函数:createX(...)(R20) | 测试不再意外变成集成测试 |
| 可能重试的操作无法安全地调用两次 | 使用幂等键、upsert 或正确的 HTTP 方法(R18) | 否则重试会破坏状态 |
以 number 存储金额 | 使用整数最小货币单位或十进制库(R19) | 浮点数无法精确表示十进制货币 |
用 === 比较浮点数 | 使用 epsilon 比较,或使用整数刻度 | 0.1 + 0.2 !== 0.3 |
在本应使用 Promise.all 的 for 循环中使用 await | 有意识地选择串行或并行 | 意外串行化是性能 bug |
每次 open 后都没有对应的 close | 使用 try/finally 或 using | 资源泄漏会在不知不觉中不断累积 |
| 新文件的模式与相邻文件不一致 | 阅读 2–3 个相邻文件并模仿其模式 | 特例代码会迫使每位读者频繁切换上下文 |
| 将重构 PR 与功能 PR 混在一起 | 拆分成两个 PR | 混合 PR 要么被敷衍批准,要么因不相关的理由被拒绝 |
| 辅助函数放在文件顶部,公共函数却在 200 行之后 | 公共接口放在前面;辅助函数按调用顺序放在后面 | 文件是从上到下阅读的 |
为操作 a 而一路访问 a.b.c.d.method() | 告诉对象做什么,而不是询问对象:将操作放到 a 上 | 调用方不应遍历私有结构 |
技术栈重点模式
以下是一些影响最大的技术栈专属规则。rules/stacks/ 下的每个规则文件都包含自己的“常见错误”部分——在对应技术栈中工作时加载该文件。
| 问题模式 | 重构为 | 位置 |
|---|---|---|
| React 组件有 6 个或更多属性(尤其是布尔值属性)来控制视觉变化 | 使用深层点号表示法的复合组件(Combobox.Content.List.Item)+ 命名空间控制 Hook(Combobox.useCombobox);通过 Context 共享状态 | stacks/react/components.md |
使用 useEffect(() => { fetch(...).then(setData) }, [id]) 获取服务端数据 | 使用 useQuery({ queryKey, queryFn })(TanStack Query / SWR) | stacks/react/data-fetching.md |
| Mutation 等待服务端响应后才更新 UI | 在 onMutate 中乐观更新,在 onError 中回滚,在 onSettled 中使缓存失效 | stacks/react/data-fetching.md |
| 表单每输入一个字符就保存 | 添加约 1000 ms 的防抖 + 最长等待约 5 秒后刷新 + 失焦时保存 + 页面可见性变为隐藏时保存;绝不阻塞输入 | stacks/react/autosave.md |
| 两个标签页自动保存同一条记录,其中一个静默覆盖另一个 | 使用 ETag + If-Match;遇到 412 时显示“已在其他位置编辑——重新加载 / 保留我的更改” | stacks/react/autosave.md |
| 重新加载后 Mutation 丢失,临时重试逻辑散布在各个组件中 | 在 IndexedDB 中使用持久化 Mutation 队列 + 为每个条目设置幂等键;通过 navigator.locks 实现多标签页单写入者 | stacks/react/offline-sync.md |
路由处理程序在未经验证的情况下信任 req.json() 请求体 | 使用共享 Zod Schema 进行 safeParse(FE + BE 导入同一个模块);从判别式 AppError 联合类型映射到统一的错误封装 | stacks/nextjs/endpoints.md |
流程
此技能有四种模式——计划(验证计划)、编写(编写新代码)、审查(重构 / 审计,仅报告发现的问题)和简化(先审查,再自动应用机械化方案)。根据上表从 $ARGUMENTS 判断模式;默认为编写模式。
编写模式(13 步检查清单)和审查模式(8 步检查流程)请加载 rules/procedure.md。简化流程请加载 rules/simplify-mode.md(划分 → 置信度门槛 → 一次应用一个方案 → 作用域内快速检查 → 失败时还原 → 最后验证整个测试套件)。
规则文件
只加载与当前代码相关的内容。每次都阅读所有规则文件只会浪费上下文。该技能的核心与语言无关——下表分为框架无关规则和技术栈专属扩展。只有代码属于某个技术栈时,才加载对应的技术栈文件。
语言无关规则
| 当代码涉及…… | 加载 |
|---|---|
| 条件语句、分支、嵌套 | rules/control-flow.md |
| 长函数或多用途函数 | rules/functions.md |
| 变量、函数、类的名称 | rules/naming.md |
| 注释、文档字符串、文档 | rules/comments.md |
| 性能问题或热点路径 | rules/performance.md |
| 错误处理、验证、防御性代码、模式优先验证、Zod / Pydantic、从模式推断类型 | rules/error-handling.md |
| 审查现有代码 | rules/review-checklist.md |
| 认知复杂度评分 | rules/cognitive-complexity.md |
| 带关联数据的联合类型、并行映射、重复常量、“应该放在哪里”、复用决策 | rules/maintainability.md |
抽象层级、类型驱动设计、确保非法状态无法表示、品牌原语、泛型、any/类型断言的使用规范 | rules/abstraction.md |
| 模块边界、公开接口、依赖方向、函数式核心 / 命令式外壳、DTO ↔ 领域 ↔ 持久化、默认不可变性、有副作用的导入 | rules/architecture.md |
| 函数签名、参数顺序、全函数、缺失值建模、错误类型系统设计、告诉而非询问、文件阅读顺序 | rules/api-design.md |
| 幂等性、金钱 / 十进制数 / 浮点数、日期和时间、标识符、编码、确定性、断言、异步 / 并发、资源管理 | rules/correctness.md |
难以测试的代码、注入时钟 / RNG / ID 作为依赖、何时调用 tdd 技能、无需 data-testid 即可通过角色 / 标签定位的 UI 组件 | rules/testability.md |
| PR 范围、相邻模式对称性、迁移与演进、处理遗留代码、差异整洁度 | rules/collaboration.md |
| 在审查输出中命名重构(R1 合并并行映射、R6 将类型声明替换为推断类型等) | rules/refactor-recipes.md |
| 在功能开发结束时自动应用机械式重构(先审查再应用、按置信度决定、失败时还原) | rules/simplify-mode.md |
特定技术栈的规则
索引及添加新技术栈的约定,请参阅 rules/stacks/README.md。
| 技术栈 | 当代码涉及…… | 加载 |
|---|---|---|
| React / Next.js | 任何 UI 文件(*.tsx、*.jsx、React Native 屏幕)——语义化 HTML、ARIA、符合 WCAG 2.2、焦点顺序、对比度、平台指南(Apple HIG、Material Design 3) | Skill('ux')(单独的技能) |
| React | 拆分组件、复合 / 命名空间组件(Component.List.Item、Component.useComponent)、插槽、RSC 边界 | rules/stacks/react/components.md |
| React | 客户端数据获取、服务端状态、查询缓存、乐观更新、缓存修改(创建 / 更新 / 删除)、查询键、请求瀑布、TanStack Query / React Query / SWR、Next.js App Router 预取、HydrationBoundary、dehydrate、initialData、每请求一个 QueryClient | rules/stacks/react/data-fetching.md |
| React | 表单自动保存、防抖 + 最大等待时间刷新、失焦 / 可见性 / pagehide 触发器、本地优先的草稿缓冲区(localStorage / IndexedDB)、带 ARIA 实时区域的状态指示器、ETag / If-Match 冲突检测、使用幂等 PATCH 的离线重试(表单基础知识交由 /ux 处理) | rules/stacks/react/autosave.md |
| React | 全应用离线 + 同步、IndexedDB 中持久化的变更队列 / outbox、前台队列与 Background Sync、TanStack Query networkMode + 持久化 + resumePausedMutations、幂等键、重新连接流程、冲突解决、多标签页协调(navigator.locks + BroadcastChannel) | rules/stacks/react/offline-sync.md |
| Next.js | 服务端端点、Route Handlers / Server Actions、Zod 边界验证、共享 Schema / 契约(前端 + 后端)、API 错误封装、HTTP 状态映射、身份验证 + 速率限制顺序、请求 ID、为整个处理函数添加带有 semconv 属性的 Span(深入内容交由 /otel-instrumentation + /otel-semantic-conventions 处理) | rules/stacks/nextjs/endpoints.md |
关键规则(始终适用)
这些规则不可妥协,因为它们反映的是人类认知的局限,而非风格偏好。
1. 线性流程胜过分支
自上而下执行的函数——先做守卫检查,再处理逻辑,最后返回——总是比有深层分支的函数更容易理解。如果 if/else if/else 跨越了 30 行,就应重构。
2. 名称承载主要信息
代码被阅读的次数是编写次数的 10 倍。一个能说明意图的 30 个字符长的名称,节省的时间远超输入它所花费的时间。反过来说,在含义可从上下文中明确判断的紧凑作用域中(循环索引、数学公式),单字母名称也完全可以。
3. 函数只描述一件事
函数名应完整描述它的功能。如果诚实的命名是 validateAndPersistAndNotifyUser,那你就有三个函数。
4. 不要为尚未出现的未来需求提前构建
针对假设性需求的配置参数、抽象层和功能标志,今天只会增加认知负担,在需求真正出现前毫无益处——而大多数需求永远不会出现。先针对眼前的情况构建;等第二种情况出现时再重构。
5. 注释解释原因,而非内容
重复代码内容的注释只是噪声。记录不明显的约束、微妙的不变量,或“我们试过 X,但因为 Y 而失败”的注释则非常宝贵。如果你想写注释,先试着重命名或提取代码,直到代码本身能表达清楚。
6. 测量后再优化
Knuth:“过早优化是万恶之源。”在分析器指出热点路径之前,优先选择易读的写法。只有约 3% 的代码会影响性能;其余 97% 的代码应优先便于人类阅读。
7. 在边界处验证,在内部信任
在边界处验证用户输入、外部 API 响应和不可信数据。不要在内部代码中到处添加防御性空值检查——它们会悄悄吞掉不可能出现的状态,从而掩盖真正的错误。如果某个值在代码的此处不可能为 null,就不要假装它可能为 null。
8. 创建之前先复用
编写辅助函数、类型、常量或格式化器之前,先在代码库中查找是否已有可复用的实现。同一概念的第二种实现比第一种更糟——行为会逐渐偏离,错误只会在其中一个副本中修复,后续读者也不知道哪一个才是规范实现。复用现有代码永远不会过早。
9. 联合类型元数据只保留一个真实来源
当联合类型或枚举有关联数据(标签、颜色、图标、标志、默认值)时,应将其放在一个以该联合类型为键、值为结构化数据的记录中,而不是放在 N 个并行映射中。添加新变体必须只需修改一处,并且类型系统必须能在遗漏字段时捕获错误。针对 OrderStatus 手动同步的四个映射迟早会酿成错误。
10. 尽量缩小改动范围
可维护的改动只涉及少量文件,并且所有文件都经过类型检查。添加某个概念的新变体之前,先问自己:“如果再添加一个,我需要修改多少处?如果漏改一处,编译器会告诉我吗?”如果答案是“很多处”或“不会”,就先重构——通常可以合并并行映射、提取重复的常量,或将数据与相关操作放在一起。参见 rules/maintainability.md §3。
11. 让非法状态无法表示
发现错误最便宜的地方,就是错误无法存在的地方。将约束纳入类型系统:状态机使用可辨识联合类型,使用带有 assertNever 的穷尽式 switch、品牌原始类型、精化类型(NonEmptyArray<T>)。类型系统本可强制执行的运行时检查只是会逐渐偏离的噪声。参见 rules/abstraction.md §2。
12. 纯核心,不纯外壳
决策逻辑应保持纯粹:不执行 I/O、不依赖时间、不使用随机数或全局状态。副作用应放在边缘的薄外壳中。纯代码最容易测试和推理;不纯代码虽不可避免,但应足够精简,以便通过集成测试验证。注入时钟、随机数生成器、获取器和 ID 生成器,而不是直接调用它们。参见 rules/architecture.md §3 和 rules/correctness.md §7。
13. 使用全函数,不要用抛错表示缺失
当每个输入都有确定的输出时,函数就是全函数。对于按设计缺失的值,返回 null;对于预期的失败,返回 Result<T, E>;仅在程序员错误和意外 I/O 失败时使用 throw。哨兵值(-1、""、0)会骗人——每个哨兵值都可能与下一个调用方遇到的真实值冲突。参见 rules/api-design.md §4。
14. 新代码先写测试
编写新函数、模块或行为时,调用 tdd 技能(Skill('tdd')),通过严格的 RED → GREEN → REFACTOR 循环推动实现。在 GREEN 阶段默默应用此技能中的规则;在 REFACTOR 阶段明确应用。仅在简单修改、重构现有代码或用户选择退出时,才跳过交接。参见 rules/testability.md。
15. UI 文件应与 ux 配合
编写或审查用于渲染 UI 的文件(*.tsx、*.jsx、*.vue、*.svelte、React Native 屏幕)时,调用 ux 技能(Skill('ux')),检查 WCAG 2.2、语义化 HTML 和平台指南。无障碍内容归 ux 负责,不在此处处理——本技能会交由其处理。应用 rules/testability.md 中有关定位器稳定性的部分(UI Testability 章节),确保无障碍名称也能提高 E2E 测试的稳定性。仅在文件与 UI 无关,或纯重构且不涉及标记时跳过。在 autonomous-workflow 第 3 阶段调用时,ux 会直接在该阶段运行——不要重复调用;遵循该阶段的调用即可。
冲突处理
当原则彼此冲突时,加载 rules/tradeoffs.md 作为权衡依据(可读性与性能、DRY 与清晰度、复用与过早抽象、简短与清晰的命名、守卫子句与单一返回、就近放置与分层文件夹)。
输出规范
在评审模式下,请将发现整理为 ## Code Quality Review: [target] 区块,并按高 / 中 / 低影响分节;如果评审代码引入或扩展了联合类型、枚举、共享常量或实用工具,还必须提供可维护性方面的发现;如果涉及可重试操作、金额、日期、异步 I/O、资源句柄或非平凡的纯逻辑,还必须提供正确性 / 可测试性方面的发现。加载 rules/output-contract.md 查看完整模板。
在编写模式下,只需编写代码(新代码可先交由 tdd 处理)。默默应用这些原则。不要逐一解释每个守卫子句——用户会在差异中看到整洁的代码。