关于 structural-review
structural-review 是用于评估代码结构、可维护性和开发体验的报告型技能。它着重提出明确、可执行的简化建议,不重复正确性和安全审查。
主要功能
- 01审查结构、可维护性和开发体验
- 02指出过大的文件和薄弱抽象
- 03检查规范层次与类型位置
- 04识别分支问题和非原子变更
- 05按严重程度排列可执行发现
适用场景
- 01检查拉取请求中的结构退化
- 02找出可删除的抽象或分支
- 03合并变更前检查可维护性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ shipshitdev/ skills --skill structural-review安装后在 Claude Code 中输入/structural-review确认已加载。
附带文件plugin.json
详细介绍
结构审查
一套观点鲜明的结构与可维护性审查准则。仅报告问题——此技能只会给出发现,不会修改文件或创建 PR。
核心原则
每条结构性发现都必须回答:“作者具体应该删除、合并或重命名什么——以及哪个具体文件/行是规范的落点?” 含糊的吹毛求疵(“这里可以更整洁”)只会制造噪声。只报告确信度高且可执行的问题。
本准则明确补充 /code-review。不要重复标记其中已涵盖的正确性 bug 或仓库指令文件违规问题。只负责与之正交的结构、可维护性和开发体验(DevEx)维度。判断变更是否让模块更深或更浅时,使用 codebase-design 术语(模块、接口、深度、接缝)。
首要审查问题
在逐项查找问题之前,先问这些问题:
- 这个 PR 让代码库变小了还是变大了? 如果变大了:每个新抽象是否物有所值,还是只是盲目增加了复杂性?
- 决策逻辑在哪里? 它是在规范层(服务端操作、领域服务、钩子)中,还是散落在渲染树和事件处理器里?
- 读者能否独立理解这个模块? 还是必须跨 4 个以上文件追踪状态/逻辑才能理解?
- 每个新类型是否放在了合适的位置? 是否用裸
unknown和as X类型断言掩盖了未来的故障?接口是否被直接内联,而不是放在应该存放它们的*.types.ts中? - PR 是否让技术栈比修改前更整洁? Tailwind v3 残留、
npm调用、shadcn 项目中的原生 HTML、Next.js 16 应用中的middleware.ts——这些都是退步,不是风格问题。
评审维度
1. 文件大小——约 1,000 行规则
文件超过约 1,000 行时,原则上应阻止合并。这不是硬性的数值规定,但强烈表明该模块承担了过多职责。
以下情况应标记:
- 此 PR 修改的任何文件现在超过 1,000 行。
- PR 本身增加了足够多的行数,使原本接近阈值的文件超过了限制。
首选修复方案: 在 PR 合并前拆分为同目录下的多个部分——*.service.ts、*.queries.ts、*.types.ts、*.hooks.ts。不要等到合并之后。
表述示例:“文件现在有 1 180 行。在发布前将查询层拆分到 *.queries.ts 中——1 000 行规则正是为这类 PR 设立的。”
严重级别: 阻止合并。
2. 抽象必须物有所值
每个新函数、钩子、类或文件都必须证明其存在的必要性。薄封装、恒等辅助函数和透传式重新导出一律不符合这一要求。
以下情况应标记:
- 新辅助函数只是单行改名,或只是调用其他函数,没有增加任何不变量。
- 新文件只是从另一个文件重新导出符号,没有做任何转换。
- 钩子只封装了一个
useState,没有相关逻辑。 - 接口只定义了一次,且只在调用处使用一次——将其内联。
首选修复方案: 删除这个抽象;在调用处内联真正的符号。Bun + TS 路径别名让原始符号很容易引用。
表述示例:“这个辅助函数只是对 formatDate 的恒等封装——将它内联并删除该文件。纯属多此一举。”
严重级别: 请求修改。
3. 意大利面式分支
在现有流程中临时添加分支,是干净的代码库变得难以维护的主要原因。
以下情况应标记:
- 同一个
if (isAdmin)/if (featureFlag)/if (isPremium)分支出现在此 PR 修改的 2 个或更多文件中。 - 在一个已有 3 条或更多条件路径的渲染函数中新增分支。
- 功能标志分支散布在组件渲染路径中,而不是在单一决策点统一处理。
首选解决方案: 将所有分支合并到一个统一的决策点——客户端状态使用 hook,变更操作使用 server action 或领域服务。这样,组件中的分支就可以消失。
措辞:“渲染树中散布着 3 个 if (isAdmin) 分支。将决策合并到一个 hook 或 server action 中,这样组件中的分支就可以消失。”
严重程度: 要求修改。
4. 规范的分层纪律
在此技术栈中,逻辑应归属于特定层。违反分层模型是结构性缺陷,而非偏好问题。
分层映射:
- 变更操作 → server action 或领域服务。不要放在原始
<form onSubmit>中,也不要放在useEffect中。 - 派生的客户端状态 → hook。不要在 JSX 中内联重新计算。
- 交互元素 → shadcn/ui 基元或
packages/ui组件。不要使用原始<button>、<input>、<dialog>。 - 路由中间件 →
proxy.ts。不要使用middleware.ts(Next.js 16)。
以下情况应标记:
- 项目中已有 server action,但变更操作直接绑定到了 DOM 事件处理器。
- 本可写成 hook 的计算逻辑被内联在 JSX 中,并在 2 个或更多组件中重复。
- 某个文件已经从
packages/ui或 shadcn 导入内容,却又出现原始 HTML 元素。 - 自定义辅助函数重复实现了代码库中已有的规范工具——应复用现有辅助函数,而不是添加一个近似重复项。
措辞:“某个组件已经从 packages/ui 导入了 Button,却还使用 <button onClick={...}>——请替换它;在此代码库中使用原始 HTML 是一种退步。”
严重程度: 要求修改;在使用 shadcn/packages/ui 的项目中,若元素是原始交互元素,则为阻断级。
5. 类型结构纪律
此项涵盖结构位置和形状问题——不包括 any 的存在,因为这由 /code-review 检查流程负责。
违规情况(全部标记):
- 没有相邻类型守卫的裸
unknown——这相当于将any延后处理,在结构上等同于未明确类型形状。 - 使用
as X断言,却没有注释说明类型系统为何无法推断出更窄的类型。 - 在组件或服务文件中内联定义接口——应放在同目录的
*.types.ts或packages/types中。
首选解决方案: 在 *.types.ts 中定义类型形状。在 unknown 进入系统的边界处添加类型守卫。
措辞:“没有类型守卫的裸 unknown 等同于延后处理的 any。请在 *.types.ts 中定义类型形状,或在此处添加守卫。”
严重程度: 没有守卫的裸 unknown → 阻断级。内联接口 → 要求修改。没有注释的 as X → 轻微。
6. 非原子变更操作
依次执行、且每次都会写入数据库的 await 调用,是与正确性密切相关的结构性缺陷。写入之间发生崩溃会使系统处于只完成部分写入的状态。
以下情况应标记:
- 同一个函数中出现 2 个或更多
await db.update()/await db.insert()调用,且没有用事务包裹。 - 某个函数先执行写入,再调用外部服务,然后执行第二次写入,却没有补偿/回滚路径。
首选解决方案: 使用数据库事务包裹,或将其建模为单次原子写入。如果外部调用无法放在事务中,则添加明确的补偿逻辑。
措辞:“这两次 await db.update() 调用不是原子的。两次调用之间发生故障会使该行处于无效状态——请用事务包裹,或将其建模为单次原子写入。”
严重程度: 阻断级。
7. 顺序编排异味
连续编排 5 个或更多步骤,且没有中间抽象的函数,会成为未来的维护隐患。它们难以测试、复用,也难以独立理解。
以下情况应标记:
- 新函数中有 5 个或更多连续的
await调用,且没有中间的具名步骤或辅助函数。 - 路由处理器或服务器操作在内联代码中完成读取、转换、验证、写入和通知。
- 两项互不相关的工作(两个无关的读取或获取操作)没有理由却被串行等待——如果并行执行也能简化流程,就应并行执行。
首选解决方案: 提取具名阶段。每个阶段都可以单独测试。编排函数则成为一份清晰易读的意图列表。
严重程度: 要求修改。
8. 技术栈整洁度(严重回归)
这不是风格建议,而是 PR 引入、必须在合并前修复的回归。
| 问题 | 规则 | 表述 |
|---|---|---|
创建或保留了 middleware.ts | Next.js 16 将入口点重命名为 proxy.ts;旧名称会被静默忽略 | “这是 middleware.ts。请重命名为 proxy.ts——Next.js 16 已重命名入口点。” |
添加了 tailwind.config.ts 或 tailwind.config.js | v4 使用基于 CSS 的配置(在 CSS 中使用 @theme);JS 配置属于 v3 | “此 PR 创建了 tailwind.config.ts——这是 v3 的做法。请删除该文件,并按 v4 的要求将主题令牌移入 CSS 的 @theme 块。” |
添加了 @apply 指令或 bg-opacity-* 类 | 这是 v3 的写法;必须迁移到 v4 的斜杠语法 | 指出确切行号和 v4 中的对应写法。 |
在脚本、CI YAML 或文档中使用 npm run、npx、yarn add 或 pnpm exec | Bun 是唯一的包管理器;请替换为 bun run / bunx / bun add | “下面两行的 npx prisma migrate 必须改为 bunx prisma migrate。” |
严重程度: 全部为阻止合并。
9. 设计纯粹性——相同行为,更简洁的结构
最关键的结构性问题不是“这样对吗?”,而是“能否用显著更少的结构表达相同行为?”在可避免的复杂性之上实现了正确行为,意味着错过了简化机会,而错过的简化会不断累积。
以下情况应标记:
- 新状态机、配置对象或分支树表达的决策,其实用一个派生值或默认值就能实现。
- 添加了特殊情况,而将其并入通用路径(采用合理的默认值或提前返回)就能彻底删除该分支。
- 重构只是重新排列代码,却没有减少读者必须同时掌握的活动部件数量。
首选解决方案: 重新构想状态模型。将特殊情况合并到默认逻辑中。删除和合并胜过润色——应通过“代码柔道”彻底消除复杂性类别,而非仅仅整理它。
表述: “这个三态标志可以简化为一个派生布尔值——行为不变,还能整个删掉一个分支。应重新构想,而非润色。”
严重程度: 要求修改(如果更简单的形式还能消除正确性隐患,则阻止合并)。
10. 直接性与魔法
优先采用显而易见的机制,而非巧妙的机制。过度通用的抽象、隐藏的假设,以及掩盖控制流的间接层,带来的阅读成本往往高于编写时节省的成本。
以下情况应标记:
- 引入通用或参数化机制,却只有一个具体调用方(“投机式泛化”)。
- 行为依赖隐藏假设——隐式排序、全局变量、命名约定、反射或元编程——而读者无法从调用位置看出这些假设。
- 使用间接层(动态分派、事件间接调用、深层配置)代替直接调用,却没有值得采用这种做法的不变量。
首选解决方案: 对唯一实际调用方内联为直接写法。在边界处明确说明假设,或者移除魔法。
表述: “只有一个处理器却使用通用注册表——删除它,直接调用处理器。等第二个调用方真正出现时,再添加抽象。”
严重程度: 要求修改。
忽略项
- 未根植于结构性缺陷的风格偏好(缩进、命名上的细微差异)。
- 正确性缺陷和测试覆盖缺口——这些应在
/code-review中处理。 - 安全问题——这些应在
/security-audit中处理。 - 正确性审查框架已涵盖的仓库指令文件规则违规。
- 文档缺口(内联文档、README 更新)——除非明确要求,否则不处理。
- 覆盖率百分比下限——仅指出新增关键路径缺少测试的问题,不要求达到特定数字。
输出格式
按严重程度排序列出发现:
## Structural Review
### Blockers (must fix before merge)
[File-size violations, non-atomic mutations, stack regressions (middleware.ts, tailwind config, npm/yarn usage), bare unknown without guard, design-purity simplifications that also remove a correctness footgun]
### Request Changes (significant structural debt)
[Abstraction-earns-keep failures, canonical-layer violations, spaghetti branching, sequential orchestration smells, inline interfaces, missed design-purity simplifications, speculative generality / magic indirection]
### Minor (low-debt, flag for awareness)
[as X casts without comments, small layer suggestions with obvious inlines]
### Approved Axes
[Axes with no findings — confirm clean]
每项发现都包括:
- 文件 + 行号范围(准确,不得近似)。
- 一句话说明结构性缺陷。
- 一句话说明具体的修复方法。
- 一条审查者措辞(使用上面的示例,或根据具体代码调整)。
批准标准
阻止和要求修改的标准已包含在上面的各个维度中。如有歧义,默认要求修改——承担歧义成本的是作者,而不是代码库。