Install any skill in seconds. Free to start, no credit card required.
Get Started Free →当用户要求重构代码(提取组件、合并重复逻辑、重命名变量、优化结构)时触发。提供重构安全检查清单,防止丢失原始上下文。
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 182% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 234% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 207% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 218% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 287% | 0% |
<instructions>
不要凭记忆或推测重构,必须完整读取原始代码:
bash# 读取完整文件 Read: file_path="src/views/Order.vue" # 如果是条件分支,读取所有分支的代码 Grep: pattern="if.*健康云" -A 50 -B 5 Grep: pattern="else" -A 50 -B 5
常见错误:
原始代码(健康云租户):
列1: 订单编号
列2: 订单日期
列3: 材料名称
列4: 材料数量
列5: 单价
列6: 金额原始代码(非健康云租户):
列1: 订单编号
列2: 创建时间
列3: 材料名称
列4: 单价
列5: 金额对比清单:
| 健康云 | 非健康云 | 状态 | |--------|---------|------| | 订单编号 | 订单编号 | ✅ 一致 | | 订单日期 | 创建时间 | ⚠️ 名称不同 | | 材料名称 | 材料名称 | ✅ 一致 | | 材料数量 | - | ❌ 非健康云无此列 | | 单价 | 单价 | ✅ 一致 | | 金额 | 金额 | ✅ 一致 |
原始接口:
typescriptinterface Order { id: string date: string items: Item[] total: number }
重构后接口:
typescriptinterface Order { id: string createdAt: string // 重命名:date → createdAt items: Item[] total: number }
对比清单:
重构完成后,必须逐项检查:
重构完成后,输出对比清单:
markdown## 重构对比 ### 原始结构(健康云租户) - 列1: 订单编号 - 列2: 订单日期 - 列3: 材料名称 - 列4: 材料数量 - 列5: 单价 - 列6: 金额 ### 原始结构(非健康云租户) - 列1: 订单编号 - 列2: 创建时间 - 列3: 材料名称 - 列4: 单价 - 列5: 金额 ### 重构后结构 - 列1: 订单编号 - 列2: 日期(健康云显示"订单日期",非健康云显示"创建时间") - 列3: 材料名称 - 列4: 材料数量(仅健康云显示) - 列5: 单价 - 列6: 金额 ### 变更说明 - 合并:订单日期 + 创建时间 → 日期(条件显示) - 条件显示:材料数量仅在健康云租户显示 - 删除:无 - 新增:无
错误示例:
javascript// 只读了健康云租户的代码 if (isHealthCloud) { columns = ['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额'] } // ❌ 假设非健康云租户只是列名不同 else { columns = ['订单编号', '创建时间', '材料名称', '材料数量', '单价', '金额'] }
正确做法:
javascript// 读取两个分支的原始代码 if (isHealthCloud) { columns = ['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额'] } else { // 非健康云租户没有"材料数量"列 columns = ['订单编号', '创建时间', '材料名称', '单价', '金额'] }
错误示例:
javascript// ❌ 凭记忆重构,没有读取原始代码 const columns = ['订单编号', '材料名称', '单价', '金额', '订单日期']
正确做法:
javascript// ✅ 读取原始代码,确认列顺序 Read: file_path="src/views/Order.vue" // 原始代码:['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额'] const columns = ['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额']
错误示例:
javascript// 重构完成,直接提交 // ❌ 没有验证列数、列顺序、列名
正确做法:
markdown## 验证清单 - [x] 列数一致:原始 6 列 → 重构后 6 列 - [x] 列顺序一致:订单编号、订单日期、材料名称、材料数量、单价、金额 - [x] 列名一致:完全一致 - [x] 没有遗漏列 - [x] 没有多余列
场景:从大服务/组件拆分子模块时,子模块需要回调父模块的方法
先提取共享方法,再拆分子服务:
拆分前:
1. 扫描父服务,识别被多个子服务调用的工具方法
2. 将这些方法提取到独立 Helper/Utils 类
3. 然后再拆分子服务(此时子服务依赖 Helper,不依赖父服务)
❌ 错误顺序:拆分子服务 → 发现循环依赖 → 修复
✅ 正确顺序:提取共享方法 → 拆分子服务 → 无循环依赖如果第一个子服务提取时已出现循环依赖,后续所有子服务提取必须先检查相同问题:
真实案例:
- ReportService → 提取 ReportInvoiceService → 循环依赖(resolveTenantIds)
- ReportService → 提取 ReportPaymentService → 同样的循环依赖!
根因:resolveTenantIds() 留在 ReportService,所有子服务都需要它
修复:应在第一次发现时就提取 TenantHelper,避免后续重复犯错错误示例:
java// ❌ ReportService 拆分出 ReportInvoiceService // 但 ReportInvoiceService 又需要调用 ReportService.resolveTenantIds() @Service @RequiredArgsConstructor public class ReportInvoiceService { private final ReportService reportService; // 循环依赖! }
正确做法(按优先级):
java// ✅ 方案1:提取公共方法到独立工具类(推荐) @Component public class TenantHelper { public List<Long> resolveTenantIds(Long tenantId) { ... } } // ✅ 方案2:@Lazy 字段注入(应急) @Service public class ReportInvoiceService { @Autowired @Lazy private ReportService reportService; } // ✅ 方案3:函数式回调 public void process(Function<Long, List<Long>> tenantResolver) { ... }
检查清单:
不仅限于 Spring:
场景:将组件内的 state 提取到自定义 Hook/composable/Service 时,封装的 open 方法添加了初始化逻辑,覆盖了调用方预设的状态
真实案例:
tsx// 重构前:调用方直接控制状态,时序正确 setMarkPaidText(selectedOrderNumbers) // 1. 设置订单号 setMarkPaidModalVisible(true) // 2. 打开弹窗 ✅ // 重构后:提取到 useOrderModals hook const openMarkPaidModal = () => { setMarkPaidText('') // ❌ 清空了调用方刚设置的值! setMarkPaidModalVisible(true) } // 调用方: setMarkPaidText(selectedOrderNumbers) // 1. 设置订单号 openMarkPaidModal() // 2. 内部又清空了 → 弹窗空白
正确做法:
tsx// ✅ 方案1:open 方法接受参数 const openMarkPaidModal = (initialText?: string) => { if (initialText !== undefined) setMarkPaidText(initialText) setMarkPaidModalVisible(true) } // ✅ 方案2:初始化放在 close 而非 open const closeMarkPaidModal = () => { setMarkPaidText('') // 关闭时清空是安全的 setMarkPaidModalVisible(false) }
检查清单:
适用范围:
场景:重构 Modal/Dialog/Drawer 等容器组件时,将复杂的 title/header/footer 简化,导致按钮布局和功能丢失
真实案例:
tsx// 重构前:title 包含条件渲染的按钮 <Modal title={ <div className="flex justify-between"> <span>发票详情</span> {!editMode && <Button icon={<EditOutlined />}>编辑发票</Button>} {editMode && ( <Space> <Button icon={<CloseOutlined />}>取消</Button> <Button type="primary" icon={<SaveOutlined />}>保存</Button> </Space> )} </div> } footer={[ <Button key="download" type="primary" icon={<DownloadOutlined />}> 下载原始PDF </Button>, <Button key="close">关闭</Button>, ]} /> // ❌ 重构后:title 简化为纯文本,footer 按钮被替换 <Modal title={`发票详情 - ${invoice?.invoiceNumber}`} // 丢失编辑/取消/保存按钮 footer={[ <button key="edit">编辑</button>, // 降级为原生 button + 丢失"下载PDF" <button key="close">关闭</button>, ]} />
检查清单:
{condition && <Button>})是否都保留<Button> 不能降级为 <button>)适用范围:
场景:重构表格列或组件时,将复杂的 render 函数(JSON 解析、条件格式化、diff 对比)替换为简单文本显示,导致功能降级
真实案例:
tsx// 重构前:~80 行的复杂 render,从 beforeValue/afterValue JSON 解析渲染 diff { title: '变更详情', key: 'changeDetails', render: (_, record) => { const oldValues = JSON.parse(record.beforeValue) const newValues = JSON.parse(record.afterValue) // ... 字段对比、颜色标记、条件渲染(新增/删除/更新) return <div>{changedKeys.map(key => ( <div>{translateFieldName(key)}: {oldValue} → {newValue}</div> ))}</div> } } // ❌ 重构后:简化为读取一个数据库中为空的字段 { title: '变更摘要', dataIndex: 'changeSummary', // 数据库中此字段为空! render: (val) => val || '-' // 全部显示 - }
检查清单:
适用范围:
场景:类型定义中有多个相似字段,重构时选错了
错误示例:
typescript// 类型定义 interface InvoiceHistory { changedAt?: string // 可选字段 createdAt: string // 必填字段 changeType?: string // 错误字段 operationType: string // 正确字段 } // ❌ 根据类型定义推测,选择了可选字段 dataIndex: 'changedAt' // 可能为 undefined → Invalid Date dataIndex: 'changeType' // 字段不存在 → 显示为空 // ❌ 枚举映射不完整 typeMap: { CREATE: { text: '创建', color: 'green' }, UPDATE: { text: '更新', color: 'blue' }, DELETE: { text: '删除', color: 'red' }, // 遗漏了 UPLOAD、OCR_START、OCR_SUCCESS 等 7 个类型 }
正确做法:
typescript// ✅ 查看原始代码的实际使用 git show HEAD~1:src/components/InvoiceHistoryTab.tsx // 原始代码使用 createdAt(必填)和 operationType(正确) dataIndex: 'createdAt' dataIndex: 'operationType' // 原始代码有 10 个枚举类型 typeMap: { UPLOAD: { text: '上传发票', color: 'blue' }, OCR_START: { text: '开始OCR', color: 'cyan' }, OCR_SUCCESS: { text: 'OCR成功', color: 'green' }, OCR_FAILED: { text: 'OCR失败', color: 'red' }, OCR_RETRY: { text: '重试OCR', color: 'orange' }, MANUAL_EDIT: { text: '手动编辑', color: 'orange' }, LINK_ORDER: { text: '关联订单', color: 'purple' }, UNLINK_ORDER: { text: '取消关联', color: 'magenta' }, FIELD_CONFIRM: { text: '确认字段', color: 'green' }, DELETE: { text: '删除发票', color: 'red' }, } // ✅ 防御性编程 render: (val: string) => { if (!val) return '-' try { return new Date(val).toLocaleString('zh-CN') } catch { return val } }
检查清单:
为什么 TypeScript 无法检测:
typescript// ❌ TypeScript 不会报错(changedAt 在类型定义中存在) dataIndex: 'changedAt' // 类型检查通过 render: (val: string) => new Date(val).toLocaleString() // 运行时:val 为 undefined → Invalid Date
场景: 同一个问题在多个文件中重复出现,修复时逐个文件加相同的补丁代码,而不是抽取共享逻辑
当一个横切关注点(如图片 URL 补全、金额格式化、权限校验)散落在多个 Service 中时,修复者容易"哪里报错修哪里",最终在 5+ 个文件里各写一份几乎相同的方法。
java// ❌ 错误: 7 个 Service 各写一份 resolveImageUrl // ProductService.java private String resolveImageUrl(String url) { if (url == null || url.startsWith("http")) return url; return minioService.getPresignedUrl(url, 60 * 24 * 7); } // MiniProductService.java — 又写一份 private String resolveImageUrl(String url) { /* 同样逻辑 */ } // CartService.java — 又写一份 private String resolveImageUrl(String url) { /* 同样逻辑 */ }
java// ✅ 正确: 先全局扫描,再抽取共享工具,一次性补齐 // 第 1 步: 全局扫描受影响的位置 // Grep: pattern="getImageUrl|mainImage|imageUrl" type="java" // 第 2 步: 抽取共享工具类 @Component public class ImageUrlResolver { private final MinioService minioService; public String resolve(String imageUrl) { if (imageUrl == null || imageUrl.isBlank()) return imageUrl; if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) { return imageUrl; } return minioService.getPresignedUrl(imageUrl, 60 * 24 * 7); } } // 第 3 步: 所有 Service 统一注入并使用
| 信号 | 说明 | |------|------| | 同一个 private 方法在 3+ 个类里出现 | 应抽成共享工具 | | 修复一个接口后,用户又报另一个接口同样的问题 | 应先全局扫描再一次性修 | | 修复 diff 里有 5+ 个文件的相同改动模式 | 应抽取公共逻辑 |
场景: 多条 list 在最终输出中按索引位置一一对应(如 [公司, 单号] / [姓名, 年龄] / [字段名, 字段值] / [问题, 答案]),重构时只对其中一条 distinct() / filter() / sorted(),其他不动 → 长度变了,索引错位,输出数据彻底乱套。
代码里两条 List 看似独立,但语义上是 zip 关系。开发者以为"去重让结果更干净",但实际上破坏了"第 i 个公司对应第 i 个单号"的隐式契约。这种错误特别隐蔽:
顺丰:1, 圆通:2, 顺丰:3 → 公司 顺丰;圆通、单号 1;2;3)java// ❌ 错误: companies 去重,numbers 不去重,破坏索引对应 List<String> companies = packages.stream() .map(ShippingPackage::getCompany) .filter(StringUtils::hasText) .distinct() .collect(Collectors.toList()); List<String> numbers = packages.stream() .map(ShippingPackage::getTrackingNo) .collect(Collectors.toList()); writeRow(sheet, "快递公司", String.join(";", companies)); writeRow(sheet, "快递单号", String.join(";", numbers)); // packages = [顺丰:SF1, 圆通:YT2, 顺丰:SF3] // companies = "顺丰;圆通" (2 段) // numbers = "SF1;SF2;SF3" (3 段) // 微信侧导入时按位置匹配 → 圆通 ↔ SF2,顺丰 ↔ SF3,物流错乱
要么两条 list 一起 distinct/filter,要么都不动:
java// ✅ 都不去重,保留所有包裹按顺序对应 List<String> companies = packages.stream() .map(ShippingPackage::getCompany) .collect(Collectors.toList()); List<String> numbers = packages.stream() .map(ShippingPackage::getTrackingNo) .collect(Collectors.toList()); if (companies.stream().distinct().count() == 1) { writeRow(sheet, "快递公司", companies.get(0)); } else { writeRow(sheet, "快递公司", String.join(";", companies)); } writeRow(sheet, "快递单号", String.join(";", numbers));
把"两条并行 list"改成"一条 list of Pair/Record",索引对应关系由语言保证:
javapublic record ShippingPackage(String company, String trackingNo) {} List<ShippingPackage> packages = order.getPackages(); String companyValue = packages.size() == 1 ? packages.get(0).company() : packages.stream().map(ShippingPackage::company).collect(joining(";")); String numberValue = packages.stream() .map(ShippingPackage::trackingNo) .collect(joining(";"));
任何后续操作(filter、sorted、partition)都作用在整个 record 上,不会出现单字段被改而其他字段不动的情况。
| 场景 | 推荐方案 | |------|---------| | 现有代码只是临时拼接,改动成本高 | 方案 A:对齐操作 | | 这两个字段在多处一起出现,或后续可能加更多字段 | 方案 B:配对结构 | | 字段超过 3 个还按位置对应 | 必须用方案 B(再多就读不懂了) |
List<Pair> / List<Record> 从根上消除并行 list完整的 Java(Stream + Record)/ Go(slice + struct)/ TypeScript(Array + interface)实现示例见 references/multi-lang-examples.md。
> 📋 本回复遵循:`refactor-safety` - [章节]</instructions>
Other measured skills in the registry, with their headline benchmark lift.