소스 정보
- 저장소
- doccker/cc-use-exp
- 최근 소스 활동
- 2026년 5월 12일 13:07
- 감지된 SKILL.md 언어
- 중국어
- 스타
- 1,006
- 포크
- 103
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/doccker/cc-use-exp --skill cc-refactor-safety명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
网关/代理/WAF/CDN 中间件的安全关键词匹配实现规范,防止纯子串匹配误判正常响应内容中的技术术语(如 Cloudflare、502、error)
涉及浏览器、编辑器、CDN/WAF、IM 平台、操作系统剪贴板、第三方 SaaS 等"外部黑盒系统"的代码编写或 bug 调试时触发。强制先抓真实环境数据再推理,避免连续 2 轮"凭代码推理"的修复 no-op。关键词:粘贴/复制异常、跨平台显示不一致、第三方 API 怪结果、CDN/WAF 拦截、本地复现失败、HTML→MD 转换丢属性。
前端开发规范,包含 Vue 3 编码规范、UI 风格约束、TypeScript 规范等
SOC 직업 분류 기준
SKILL.md 표시 중
| name | cc-refactor-safety |
| description | 当用户要求重构代码(提取组件、合并重复逻辑、重命名变量、优化结构)时触发。提供重构安全检查清单,防止丢失原始上下文。 |
不要凭记忆或推测重构,必须完整读取原始代码:
# 读取完整文件
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: 金额
对比清单:
| 健康云 | 非健康云 | 状态 |
|---|---|---|
| 订单编号 | 订单编号 | ✅ 一致 |
| 订单日期 | 创建时间 | ⚠️ 名称不同 |
| 材料名称 | 材料名称 | ✅ 一致 |
| 材料数量 | - | ❌ 非健康云无此列 |
| 单价 | 单价 | ✅ 一致 |
| 金额 | 金额 | ✅ 一致 |
原始接口:
interface Order {
id: string
date: string
items: Item[]
total: number
}
重构后接口:
interface Order {
id: string
createdAt: string // 重命名:date → createdAt
items: Item[]
total: number
}
对比清单:
重构完成后,必须逐项检查:
重构完成后,输出对比清单:
## 重构对比
### 原始结构(健康云租户)
- 列1: 订单编号
- 列2: 订单日期
- 列3: 材料名称
- 列4: 材料数量
- 列5: 单价
- 列6: 金额
### 原始结构(非健康云租户)
- 列1: 订单编号
- 列2: 创建时间
- 列3: 材料名称
- 列4: 单价
- 列5: 金额
### 重构后结构
- 列1: 订单编号
- 列2: 日期(健康云显示"订单日期",非健康云显示"创建时间")
- 列3: 材料名称
- 列4: 材料数量(仅健康云显示)
- 列5: 单价
- 列6: 金额
### 变更说明
- 合并:订单日期 + 创建时间 → 日期(条件显示)
- 条件显示:材料数量仅在健康云租户显示
- 删除:无
- 新增:无
错误示例:
// 只读了健康云租户的代码
if (isHealthCloud) {
columns = ['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额']
}
// ❌ 假设非健康云租户只是列名不同
else {
columns = ['订单编号', '创建时间', '材料名称', '材料数量', '单价', '金额']
}
正确做法:
// 读取两个分支的原始代码
if (isHealthCloud) {
columns = ['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额']
} else {
// 非健康云租户没有"材料数量"列
columns = ['订单编号', '创建时间', '材料名称', '单价', '金额']
}
错误示例:
// ❌ 凭记忆重构,没有读取原始代码
const columns = ['订单编号', '材料名称', '单价', '金额', '订单日期']
正确做法:
// ✅ 读取原始代码,确认列顺序
Read: file_path="src/views/Order.vue"
// 原始代码:['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额']
const columns = ['订单编号', '订单日期', '材料名称', '材料数量', '单价', '金额']
错误示例:
// 重构完成,直接提交
// ❌ 没有验证列数、列顺序、列名
正确做法:
## 验证清单
- [x] 列数一致:原始 6 列 → 重构后 6 列
- [x] 列顺序一致:订单编号、订单日期、材料名称、材料数量、单价、金额
- [x] 列名一致:完全一致
- [x] 没有遗漏列
- [x] 没有多余列
场景:从大服务/组件拆分子模块时,子模块需要回调父模块的方法
先提取共享方法,再拆分子服务:
拆分前:
1. 扫描父服务,识别被多个子服务调用的工具方法
2. 将这些方法提取到独立 Helper/Utils 类
3. 然后再拆分子服务(此时子服务依赖 Helper,不依赖父服务)
❌ 错误顺序:拆分子服务 → 发现循环依赖 → 修复
✅ 正确顺序:提取共享方法 → 拆分子服务 → 无循环依赖
如果第一个子服务提取时已出现循环依赖,后续所有子服务提取必须先检查相同问题:
真实案例:
- ReportService → 提取 ReportInvoiceService → 循环依赖(resolveTenantIds)
- ReportService → 提取 ReportPaymentService → 同样的循环依赖!
根因:resolveTenantIds() 留在 ReportService,所有子服务都需要它
修复:应在第一次发现时就提取 TenantHelper,避免后续重复犯错
错误示例:
// ❌ ReportService 拆分出 ReportInvoiceService
// 但 ReportInvoiceService 又需要调用 ReportService.resolveTenantIds()
@Service
@RequiredArgsConstructor
public class ReportInvoiceService {
private final ReportService reportService; // 循环依赖!
}
正确做法(按优先级):
// ✅ 方案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 方法添加了初始化逻辑,覆盖了调用方预设的状态
真实案例:
// 重构前:调用方直接控制状态,时序正确
setMarkPaidText(selectedOrderNumbers) // 1. 设置订单号
setMarkPaidModalVisible(true) // 2. 打开弹窗 ✅
// 重构后:提取到 useOrderModals hook
const openMarkPaidModal = () => {
setMarkPaidText('') // ❌ 清空了调用方刚设置的值!
setMarkPaidModalVisible(true)
}
// 调用方:
setMarkPaidText(selectedOrderNumbers) // 1. 设置订单号
openMarkPaidModal() // 2. 内部又清空了 → 弹窗空白
正确做法:
// ✅ 方案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 简化,导致按钮布局和功能丢失
真实案例:
// 重构前: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 按钮被替换
编辑, // 降级为原生 button + 丢失"下载PDF"
关闭,
]}
/>
检查清单:
{condition && <Button>})是否都保留<Button> 不能降级为 <button>)适用范围:
场景:重构表格列或组件时,将复杂的 render 函数(JSON 解析、条件格式化、diff 对比)替换为简单文本显示,导致功能降级
真实案例:
// 重构前:~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 || '-' // 全部显示 -
}
检查清单:
适用范围:
场景:类型定义中有多个相似字段,重构时选错了
错误示例:
// 类型定义
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 个类型
}
正确做法:
// ✅ 查看原始代码的实际使用
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: '确认字段', : },
: { : , : },
}
: {
(!val)
{
(val).()
} {
val
}
}
检查清单:
为什么 TypeScript 无法检测:
// ❌ TypeScript 不会报错(changedAt 在类型定义中存在)
dataIndex: 'changedAt' // 类型检查通过
render: (val: string) => new Date(val).toLocaleString()
// 运行时:val 为 undefined → Invalid Date
场景: 同一个问题在多个文件中重复出现,修复时逐个文件加相同的补丁代码,而不是抽取共享逻辑
当一个横切关注点(如图片 URL 补全、金额格式化、权限校验)散落在多个 Service 中时,修复者容易"哪里报错修哪里",最终在 5+ 个文件里各写一份几乎相同的方法。
// ❌ 错误: 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) { /* 同样逻辑 */ }
// ✅ 正确: 先全局扫描,再抽取共享工具,一次性补齐
// 第 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)// ❌ 错误: 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,要么都不动:
// ✅ 都不去重,保留所有包裹按顺序对应
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",索引对应关系由语言保证:
public 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` - [章节]