ddt
DDT 表示 dont-do-that packaging。
这个 skill 用来收掉没有真实收益的包装层,让代码更贴近人类阅读路径。目标不是“越少越好”,而是判断一层函数、类型、常量、文件或参数传递是否真的降低了理解成本。
先读项目规范
如果要审查或修改项目代码,先读取仓库内的 CLAUDE.md 和存在的 AGENTS.md,再读取相关模块旁的说明文件。判断时同时看项目规范、用户长期约束和当前代码,不要只按 DDT 规则下结论。
如果项目规范和 DDT 冲突,优先遵守更具体的项目规范;同时指出这会保留某些看起来偏薄的结构。类型文件命名和放置也优先按项目类型规范处理;只有项目没有明确类型文件约束时,才把业务 TS 源文件剥离出的类型放到同目录 {源文件名}.d.ts。
选择引用资料
按当前任务读取对应引用资料,不要一次性加载无关内容。
- React 组件、React hook、
.tsx、组件配套util文件、前端状态链路:读取references/react/index.md。 - Node.js 后台、service / query / logic、数据库读写、Kysely、接口 DTO、后端类型与枚举:读取
references/nodejs/index.md。 - 纯通用问题,例如小函数、薄类型、单点常量、实例属性参数绕行:只用本文件即可。
核心判断
先判断拆分有没有真实收益,再决定保留还是收回。
看到一层函数 / 类型 / 常量 / 文件 / 参数传递 / 兜底分支
│
▼
它是否被多个地方稳定复用?
├─ 是 ──► 保留,并确认命名和注释能表达业务语义
│
└─ 否
│
▼
它是否明显降低主流程认知负担?
├─ 是 ──► 保留,但避免继续拆更薄的层
│
└─ 否
│
▼
它是否表达独立业务边界、外部协议、测试边界或项目强约束?
├─ 是 ──► 保留,并让边界更清楚
│
└─ 否 ──► 收回:内联函数、合并类型、删除透传参数、中间层或重复兜底
通用流程
开始 DDT 检查
│
▼
读取 CLAUDE.md / AGENTS.md / 模块说明
│
▼
按文件类型选择引用资料
├─ React / TSX / hook ──► references/react/index.md
├─ Node.js 后台 ────────► references/nodejs/index.md
└─ 通用结构问题 ───────► 留在 SKILL.md
│
▼
列出候选包装层
│
▼
逐项判断收益:复用、主流程、边界、测试、项目规范
│
▼
采取最小动作
├─ 无收益 ──► 内联 / 合并 / 删除绕行 / 收回单点常量
└─ 有收益 ──► 保留,并改善命名、注释或放置位置
│
▼
更新 import、类型引用和必要注释
│
▼
运行最小必要验证并报告结果
通用规则
如果一个函数只有很少几行,只有一个调用方,而且函数名没有提供新的业务语义,默认这是过度封装,应该内联回调用处。不要为了看起来更模块化就保留这种薄函数。
如果一个类型只是从另一个类型里薄薄切出一层,没有形成稳定复用,也没有显著降低理解成本,默认合并回更直接的类型定义。只有在子类型真的被多个地方独立使用,或者拆出来以后能明显降低主类型复杂度时,才保留拆分。
如果业务 .ts / .tsx 源文件确实需要把类型从主文件剥离,先读取项目类型文件规则、同目录既有模式和上层模块约定。项目有规定时按项目规定,例如 types.ts、<api-file>.types.ts、src/types/*.d.ts 或其他约定位置;项目没有规定时,才使用同目录 {源文件名}.d.ts,例如 ChatComponent.tsx 对应 ChatComponent.d.ts,agent.service.ts 对应 agent.service.d.ts。不要为了单个源文件新增和项目规则冲突的泛化类型收集文件。
如果一个 enum、const、as const 对象、延迟时间、阈值、固定 key、mode、status 或 tool name 只有一个使用处,默认直接耦合到使用处。只有跨多个文件、多个分支、多个业务动作复用,或确实表达外部协议 / 复杂业务边界时,才抽到 enum/** 或常量文件。
class 内部已经稳定存在的实例状态,不要再为了“显式传参”沿着私有方法链路传递。私有方法本来就是实例行为,直接读 this.xxx 往往比 const xxx = this.xxx; this.a(xxx); this.b(xxx); 更能表达这个值属于对象状态。
兜底逻辑只在输入来自不稳定外部边界,或者缺少兜底会造成明确错误时保留。状态已经由生命周期、事件顺序、状态机或类型定义保证时,不要为了“更稳”保留重复防御分支。
代码优先服务人类阅读,不优先服务形式上的解耦。局部轻微耦合通常比多跳一层更好读。
判断表
| 对象 | 倾向收回 | 倾向保留 |
|---|---|---|
| 小函数 | 2 到 5 行、单一调用方、函数名复述实现 | 多处稳定复用、表达业务动作、需要独立测试 |
| 类型 | 只是一层别名、摘字段、消费范围很窄 | 外部契约、消息体、事件载荷、稳定嵌套结构 |
| 常量 / enum | 只有一处引用、上下文已经自解释 | 多处复用、外部协议、复杂业务边界 |
| 参数 | 调用方只是从 this 读出再传给私有方法 |
参数来自调用现场,或每次调用会变化 |
| 兜底 | 已由状态机、生命周期或类型保证不会触发 | 外部输入不稳定,或缺少保护会造成错误 |
执行动作
当确认存在过度包装时,直接做这些事。
| 问题 | 动作 |
|---|---|
| 单一调用方的小函数 | 内联回调用处,删掉 helper |
| 无收益的中间层 | 删除透传层,让调用关系更短 |
| 过薄的类型 | 合并回主类型或更直接的边界类型 |
| 需要从业务 TS 源文件剥离的类型 | 先按项目类型规则;无约束时用同目录 {源文件名}.d.ts |
| 单一引用的 enum 或常量 | 内联回使用处,删掉 enum / const |
| 只是转手传递的实例属性参数 | 让目标私有方法直接读取 this.xxx |
| 主流程已经保证不会触发的兜底分支 | 删除重复防御 |
| 必要注释被薄封装掩盖 | 保留注释,但只解释关键意图 |
修改后检查 import、循环引用、类型导入、JSDoc 和项目要求的最小验证。验证范围按影响面选择,至少覆盖受影响文件的 lint 或类型检查。
输出要求
如果用户要求直接修,就完成修改和验证后再回复结果。
如果用户要求 review,就按文件指出哪些函数、类型、常量或文件职责属于 DDT 问题,并给出最小修改建议。不要泛泛评价“抽象层次”。
解释原因时用短句说明即可,例如“只有一个调用方且逻辑过薄,内联后阅读路径更短”。