dry-refactoring

安装量: 36
排名: #19194

安装

npx skills add https://github.com/yyh211/claude-meta-skill --skill dry-refactoring

DRY 标准化流程:从识别到重构

这个技能指导你系统性地应用 DRY (Don't Repeat Yourself) 原则,通过四步标准化流程消除代码重复,提升代码质量和可维护性。

When to Use This Skill

使用此技能当用户请求:

消除代码重复或冗余 重构有明显复制粘贴痕迹的代码 应用 DRY 原则优化代码库 识别并修复"代码坏味道"(如魔术数字、重复逻辑) 提取公共逻辑为可复用单元 改善代码的可维护性

关键触发词: DRY, 重复代码, 代码重复, 重构, 消除重复, 复制粘贴, 魔术数字, 代码坏味道, 抽象, 提取函数

核心思想

系统中的每一处知识都必须拥有一个单一、明确、权威的表示。

这意味着:

任何业务逻辑、算法或配置信息都应该只存在于代码库的一个地方 如果需要修改,你只需改这一个地方 修改会自动反映到所有使用该逻辑的地方

两次法则 (Rule of Two): 当你第二次写下几乎相同的代码块时,警钟就应该敲响。这是开始重构的信号。

四步标准化流程

这是一个可在编码任何阶段应用的微循环。严格按照步骤执行,确保重构的安全性和有效性。

第一步:识别重复 (Identify Repetition)

目标: 像侦探一样,对代码中的"坏味道"保持警惕,找出所有重复。

1.1 明显的重复

直接复制粘贴:

两块或多块代码长得几乎一模一样 只有变量名或少数值不同 这是最明显、最需要被消除的重复

示例:

// 重复 1 function calculateOrderDiscount(orderTotal) { if (orderTotal > 100) { return orderTotal * 0.1; } return 0; }

// 重复 2 function calculateCouponDiscount(couponTotal) { if (couponTotal > 100) { return couponTotal * 0.1; } return 0; }

"魔术数字"或字符串:

同一个配置值或字符串在多处以字面量形式出现 例如:0.08、"http://api.example.com"、100

示例:

魔术数字重复

def calculate_tax_1(amount): return amount * 0.08 # ❌ 魔术数字

def calculate_tax_2(amount): return amount * 0.08 # ❌ 再次出现

def calculate_total(amount): tax = amount * 0.08 # ❌ 第三次 return amount + tax

1.2 语义上的重复

结构性重复:

代码结构相似,但具体变量名或值不同 多个 if-else 结构都在做类似的条件判断和赋值

示例:

// 结构性重复 function processUserData(user: User) { if (user.age >= 18) { user.status = 'adult'; } else { user.status = 'minor'; } }

function processProductData(product: Product) { if (product.price >= 100) { product.category = 'premium'; } else { product.category = 'standard'; } }

逻辑重复:

两个不同的函数,代码看起来不一样 但它们在业务逻辑层面实现的是同一个目标

示例:

// 逻辑重复:都在计算折扣,只是来源不同 function applyMembershipDiscount(price, memberLevel) { const discountRates = { gold: 0.2, silver: 0.1, bronze: 0.05 }; return price * (1 - (discountRates[memberLevel] || 0)); }

function applySeasonalDiscount(price, season) { const discountRates = { winter: 0.2, spring: 0.1, summer: 0.05 }; return price * (1 - (discountRates[season] || 0)); }

识别清单

当你审查代码时,检查以下信号:

复制粘贴的代码块(完全相同或高度相似) 相同的数字、字符串在多处出现 相似的 if-else 或 switch-case 结构 功能相似但命名不同的函数 相同的算法在不同地方重新实现 相同的验证逻辑分散在多个地方

💡 Action: 使用搜索功能查找重复的字面量、相似的函数名模式。记录所有重复出现的位置。

第二步:抽象逻辑 (Abstract the Logic)

目标: 将重复的逻辑提取出来,封装到一个独立、可复用的单元中。

2.1 识别可变与不变部分

不变部分:

这是重复的核心逻辑 每次重复时都保持不变的代码 这将成为你的抽象主体

可变部分:

每次重复时发生变化的东西 不同的值、变量名、配置 这些将成为函数或类的参数

分析示例:

// 原始重复代码 const userEmail = validateEmail(user.email); const adminEmail = validateEmail(admin.email); const supportEmail = validateEmail(support.email);

// 分析: // 不变部分:validateEmail() 调用 // 可变部分:不同的 email 值

2.2 选择合适的抽象形式

根据重复的特点,选择最合适的抽象方式:

抽象形式 适用场景 示例 函数 (Function) 封装一段算法或行为 计算折扣、验证输入、格式化数据 类 (Class) 封装行为 + 关联状态 用户管理器、数据处理器、配置管理器 模块/组件 一组相关的函数、类和配置 认证模块、日志模块、API 客户端 配置文件/常量 重复的魔术数字或字符串 API 端点、税率、阈值 高阶函数 重复的控制流程或模式 重试逻辑、缓存包装、错误处理 2.3 设计抽象接口

函数抽象示例:

❌ 重复代码

def process_user_order(user_id, order_data): user = db.query(User).filter_by(id=user_id).first() if not user: raise ValueError("User not found") # 处理订单...

def process_user_payment(user_id, payment_data): user = db.query(User).filter_by(id=user_id).first() if not user: raise ValueError("User not found") # 处理支付...

✅ 抽象后

def get_user_or_error(user_id): """不变部分:获取用户并验证""" user = db.query(User).filter_by(id=user_id).first() if not user: raise ValueError("User not found") return user

def process_user_order(user_id, order_data): user = get_user_or_error(user_id) # 可变部分:user_id # 处理订单...

def process_user_payment(user_id, payment_data): user = get_user_or_error(user_id) # 可变部分:user_id # 处理支付...

常量抽象示例:

// ❌ 魔术数字 function calculateTax(amount) { return amount * 0.08; }

function displayTaxInfo(amount) { console.log(Tax (8%): $${amount * 0.08}); }

// ✅ 抽象为常量 const TAX_RATE = 0.08;

function calculateTax(amount) { return amount * TAX_RATE; }

function displayTaxInfo(amount) { console.log(Tax (${TAX_RATE * 100}%): $${amount * TAX_RATE}); }

类抽象示例:

// ❌ 重复的状态和行为 const userCache = new Map(); function getUserFromCache(id: string) { /.../ } function setUserInCache(id: string, user: User) { /.../ }

const productCache = new Map(); function getProductFromCache(id: string) { /.../ } function setProductInCache(id: string, product: Product) { /.../ }

// ✅ 抽象为类 class Cache { private store = new Map();

get(id: string): T | undefined { return this.store.get(id); }

set(id: string, value: T): void { this.store.set(id, value); } }

const userCache = new Cache(); const productCache = new Cache();

抽象设计原则

Do:

✅ 参数化可变部分(值、配置、行为) ✅ 保持接口简单(参数数量 ≤ 4 个) ✅ 使用描述性命名(说明"做什么"而非"怎么做") ✅ 考虑未来的扩展性(但不要过度设计)

Don't:

❌ 创建过于通用的抽象("万能函数") ❌ 过早抽象(只有一次使用时不要抽象) ❌ 忽略性能影响(例如不必要的函数调用开销) ❌ 使用难以理解的抽象(增加认知负担)

💡 Action: 创建一个新的函数、类或配置文件,将"不变部分"放进去,将"可变部分"定义为参数。

第三步:替换实现 (Replace the Implementation)

目标: 用新的抽象单元替换所有旧的重复代码。

3.1 系统性替换

步骤:

定位所有重复点:回到第一步记录的所有位置 逐一替换:删除旧代码,调用新抽象 传入正确参数:确保参数对应正确 保持行为一致:确保替换前后功能完全相同

替换示例:

Before (重复代码):

位置 1: user_service.py

def create_user(data): if not data.get('email'): return {'error': 'Email is required'}, 400 if not data.get('password'): return {'error': 'Password is required'}, 400 # 创建用户...

位置 2: product_service.py

def create_product(data): if not data.get('name'): return {'error': 'Name is required'}, 400 if not data.get('price'): return {'error': 'Price is required'}, 400 # 创建产品...

After (使用抽象):

新抽象: validation_utils.py

def validate_required_fields(data, required_fields): """验证必填字段""" for field in required_fields: if not data.get(field): return {'error': f'{field.capitalize()} is required'}, 400 return None

位置 1: user_service.py (已替换)

def create_user(data): error = validate_required_fields(data, ['email', 'password']) if error: return error # 创建用户...

位置 2: product_service.py (已替换)

def create_product(data): error = validate_required_fields(data, ['name', 'price']) if error: return error # 创建产品...

3.2 处理边缘情况

有时候重复代码之间存在细微差异,需要特殊处理:

策略 1:添加可选参数

// 大部分重复,但有一个地方需要额外日志 function processData(data, options = {}) { // 通用处理...

if (options.enableLogging) { console.log('Processing:', data); }

return result; }

// 使用 processData(data1); // 无日志 processData(data2, { enableLogging: true }); // 有日志

策略 2:回调函数

// 核心流程相同,但中间步骤不同 function processWithCustomStep( data: T, customStep: (item: T) => T ): T { // 前置处理 const prepared = prepare(data);

// 可变的自定义步骤 const processed = customStep(prepared);

// 后置处理 return finalize(processed); }

// 使用 processWithCustomStep(userData, (user) => validateUser(user)); processWithCustomStep(productData, (product) => enrichProduct(product));

策略 3:保留特殊情况

如果某个重复有本质上的不同,考虑保留它

def process_standard_order(order): return apply_dry_abstraction(order, 'standard')

def process_vip_order(order): # VIP 订单有完全不同的业务逻辑,不强行抽象 # 保留独立实现 pass

替换清单 确认所有重复点都已替换(不要遗漏) 删除旧的重复代码(避免混用新旧方式) 检查导入语句和依赖关系 确保参数顺序和类型正确 处理了所有边缘情况

⚠️ 警告: 如果只替换了一部分,你就创造了另一种不一致,情况可能更糟。确保全部替换或全部不替换。

💡 Action: 使用 IDE 的"查找所有引用"功能,确保没有遗漏任何重复点。

第四步:验证与测试 (Verify and Test)

目标: 确保重构没有破坏任何功能,程序行为在重构前后完全一致。

4.1 单元测试

为你新创建的抽象编写独立的单元测试:

测试覆盖要点:

✅ 正常输入的正确输出 ✅ 边界值测试(空值、最大值、最小值) ✅ 异常输入的错误处理 ✅ 不同参数组合的行为

示例:

抽象函数

def calculate_discount(price, discount_rate): """计算折扣后价格""" if not 0 <= discount_rate <= 1: raise ValueError("Discount rate must be between 0 and 1") return price * (1 - discount_rate)

单元测试

def test_calculate_discount(): # 正常情况 assert calculate_discount(100, 0.1) == 90 assert calculate_discount(100, 0) == 100

# 边界情况
assert calculate_discount(0, 0.5) == 0
assert calculate_discount(100, 1) == 0

# 异常情况
with pytest.raises(ValueError):
    calculate_discount(100, 1.5)
with pytest.raises(ValueError):
    calculate_discount(100, -0.1)

4.2 集成测试

运行那些覆盖了被修改代码区域的集成测试:

运行特定模块的测试

pytest tests/test_user_service.py pytest tests/test_product_service.py

或运行整个测试套件

npm test pytest

检查要点:

所有测试都通过 没有新的失败或错误 性能没有显著下降 覆盖率没有降低 4.3 手动验证

如果没有自动化测试(或测试覆盖不足),进行手动验证:

验证清单:

启动应用程序,检查是否正常运行 测试被修改的功能(通过 UI 或 API) 检查日志输出是否正常 测试错误场景(无效输入、边界条件) 在不同环境中测试(开发、测试、预发布) 4.4 性能验证

确保抽象没有引入性能问题:

import time

性能测试

def benchmark_function(func, args, iterations=1000): start = time.time() for _ in range(iterations): func(args) end = time.time() return (end - start) / iterations

对比重构前后

old_time = benchmark_function(old_implementation, test_data) new_time = benchmark_function(new_implementation, test_data)

print(f"Old: {old_time:.6f}s, New: {new_time:.6f}s") print(f"Difference: {((new_time - old_time) / old_time * 100):.2f}%")

4.5 代码审查

如果在团队中工作,进行代码审查:

审查要点:

抽象是否合理且易于理解? 命名是否清晰且符合约定? 是否有遗漏的重复点? 是否过度抽象或设计复杂? 文档和注释是否充分?

💡 Action: 运行所有相关测试,确保程序的外部行为在重构前后完全一致。没有测试?现在是编写测试的最佳时机。

完整示例:从头到尾 场景:电商系统的折扣计算 原始代码(存在重复) // order_service.js function calculateOrderTotal(order) { let total = 0; for (const item of order.items) { total += item.price * item.quantity; }

// 会员折扣 if (order.memberLevel === 'gold') { total = total * 0.8; // ❌ 魔术数字 } else if (order.memberLevel === 'silver') { total = total * 0.9; // ❌ 魔术数字 }

return total; }

// cart_service.js function calculateCartTotal(cart) { let total = 0; for (const item of cart.items) { total += item.price * item.quantity; // ❌ 重复计算逻辑 }

// 优惠券折扣 if (cart.couponType === 'premium') { total = total * 0.8; // ❌ 重复的折扣计算 } else if (cart.couponType === 'standard') { total = total * 0.9; // ❌ 重复的折扣计算 }

return total; }

步骤 1:识别重复

发现的重复:

计算商品总价的循环逻辑(结构重复) 折扣计算逻辑(逻辑重复) 魔术数字 0.8 和 0.9(明显重复) 步骤 2:抽象逻辑 // pricing_utils.js (新建)

// 抽象 1:商品总价计算 function calculateItemsTotal(items) { return items.reduce((total, item) => { return total + (item.price * item.quantity); }, 0); }

// 抽象 2:折扣配置(消除魔术数字) const DISCOUNT_RATES = { membership: { gold: 0.2, // 20% off silver: 0.1, // 10% off bronze: 0.05 // 5% off }, coupon: { premium: 0.2, // 20% off standard: 0.1, // 10% off basic: 0.05 // 5% off } };

// 抽象 3:应用折扣 function applyDiscount(amount, discountRate) { if (discountRate < 0 || discountRate > 1) { throw new Error('Invalid discount rate'); } return amount * (1 - discountRate); }

// 抽象 4:获取折扣率 function getDiscountRate(category, level) { return DISCOUNT_RATES[category]?.[level] || 0; }

export { calculateItemsTotal, applyDiscount, getDiscountRate };

步骤 3:替换实现 // order_service.js (重构后) import { calculateItemsTotal, applyDiscount, getDiscountRate } from './pricing_utils.js';

function calculateOrderTotal(order) { const subtotal = calculateItemsTotal(order.items); const discountRate = getDiscountRate('membership', order.memberLevel); return applyDiscount(subtotal, discountRate); }

// cart_service.js (重构后) import { calculateItemsTotal, applyDiscount, getDiscountRate } from './pricing_utils.js';

function calculateCartTotal(cart) { const subtotal = calculateItemsTotal(cart.items); const discountRate = getDiscountRate('coupon', cart.couponType); return applyDiscount(subtotal, discountRate); }

步骤 4:验证与测试 // pricing_utils.test.js import { calculateItemsTotal, applyDiscount, getDiscountRate } from './pricing_utils.js';

describe('Pricing Utils', () => { describe('calculateItemsTotal', () => { it('should calculate total for multiple items', () => { const items = [ { price: 10, quantity: 2 }, { price: 5, quantity: 3 } ]; expect(calculateItemsTotal(items)).toBe(35); });

it('should return 0 for empty items', () => {
  expect(calculateItemsTotal([])).toBe(0);
});

});

describe('applyDiscount', () => { it('should apply 20% discount correctly', () => { expect(applyDiscount(100, 0.2)).toBe(80); });

it('should throw error for invalid discount rate', () => {
  expect(() => applyDiscount(100, 1.5)).toThrow('Invalid discount rate');
});

});

describe('getDiscountRate', () => { it('should return correct membership discount', () => { expect(getDiscountRate('membership', 'gold')).toBe(0.2); });

it('should return 0 for unknown level', () => {
  expect(getDiscountRate('membership', 'unknown')).toBe(0);
});

}); });

// 运行测试 // npm test pricing_utils.test.js

重构成果:

✅ 消除了所有重复代码 ✅ 魔术数字集中管理 ✅ 每个函数职责单一 ✅ 易于测试和维护 ✅ 如果需要添加新的会员等级或折扣类型,只需修改 DISCOUNT_RATES 常见陷阱与解决方案 陷阱 1:过度抽象 (Over-Abstraction)

症状:创建了过于通用、难以理解的抽象。

示例:

// ❌ 过度抽象 function universalProcessor(data, options, callbacks, config, meta) { // 100 行通用处理逻辑... }

// ✅ 合理抽象 function processUserData(user) { return validate(user) && transform(user); }

解决方案:

只在有明确重复时才抽象 保持抽象简单明了 如果参数超过 4 个,考虑重新设计 陷阱 2:不完全替换

症状:只替换了部分重复点,留下了一些旧代码。

后果:

代码库中存在新旧两种实现 未来修改时容易遗漏 造成新的不一致

解决方案:

使用全局搜索确保找到所有重复 一次性完成所有替换 使用 linter 或静态分析工具检测未使用的代码 陷阱 3:忽略性能影响

症状:抽象引入了不必要的性能开销。

示例:

❌ 每次调用都重新编译正则表达式

def validate_email(email): return re.match(r'^[\w.-]+@[\w.-]+.\w+$', email)

✅ 复用编译后的正则表达式

EMAIL_PATTERN = re.compile(r'^[\w.-]+@[\w.-]+.\w+$')

def validate_email(email): return EMAIL_PATTERN.match(email)

解决方案:

对性能敏感的代码进行基准测试 使用缓存、记忆化等优化技术 必要时使用性能分析工具 陷阱 4:破坏封装性

症状:抽象暴露了过多内部实现细节。

示例:

// ❌ 暴露内部状态 class UserManager { public users: Map; // 直接暴露内部数据结构

getUser(id: string) { return this.users.get(id); } }

// ✅ 隐藏内部实现 class UserManager { private users: Map;

getUser(id: string): User | undefined { return this.users.get(id); }

addUser(user: User): void { this.users.set(user.id, user); } }

解决方案:

使用访问控制(private, protected) 提供明确的公共接口 隐藏实现细节 最佳实践 1. 渐进式重构

不要试图一次性重构整个代码库:

策略:

✅ 每次重构一个小的重复区域 ✅ 重构后立即测试 ✅ 提交小的、原子性的变更 ✅ 逐步扩大重构范围

示例工作流:

1. 创建特性分支

git checkout -b refactor/dry-pricing-logic

2. 重构一个模块

编辑 pricing_utils.js

3. 测试

npm test

4. 提交

git add pricing_utils.js git commit -m "Extract pricing calculation to reusable utility"

5. 重构使用该模块的文件

编辑 order_service.js

6. 再次测试和提交

npm test git add order_service.js git commit -m "Refactor order service to use pricing utility"

7. 继续其他模块...

  1. 文档化你的抽象

好的抽象需要好的文档:

/* * 计算商品折扣后的价格 * * @param price - 原始价格(必须 >= 0) * @param discountRate - 折扣率(0-1 之间,0.2 表示 20% 折扣) * @returns 折扣后的价格 * @throws {Error} 如果 discountRate 不在有效范围内 * * @example * applyDiscount(100, 0.2) // 返回 80 * applyDiscount(50, 0) // 返回 50(无折扣) / function applyDiscount(price: number, discountRate: number): number { if (discountRate < 0 || discountRate > 1) { throw new Error(Invalid discount rate: ${discountRate}. Must be between 0 and 1.); } return price * (1 - discountRate); }

  1. 使用类型系统

利用类型系统防止误用:

// 使用类型别名增强可读性 type DiscountRate = number; // 0-1 之间 type Price = number; // >= 0

// 更好:使用品牌类型确保类型安全 type DiscountRate = number & { __brand: 'DiscountRate' };

function createDiscountRate(value: number): DiscountRate { if (value < 0 || value > 1) { throw new Error('Discount rate must be between 0 and 1'); } return value as DiscountRate; }

function applyDiscount(price: Price, discountRate: DiscountRate): Price { return (price * (1 - discountRate)) as Price; }

// 使用 const rate = createDiscountRate(0.2); // 类型检查通过 applyDiscount(100, rate);

// applyDiscount(100, 0.2); // ❌ 类型错误!必须使用 createDiscountRate

  1. 重构前先写测试

如果没有测试,先写测试再重构:

// 步骤 1:为现有(重复的)代码写测试 describe('Original Implementation', () => { it('should calculate order total correctly', () => { const order = { items: [{ price: 10, quantity: 2 }], memberLevel: 'gold' }; expect(calculateOrderTotal(order)).toBe(16); // 20 * 0.8 }); });

// 步骤 2:重构代码

// 步骤 3:确保测试仍然通过 // npm test

检查清单

在完成 DRY 重构后,验证以下内容:

识别阶段 找到了所有明显的代码重复 识别了魔术数字和硬编码字符串 发现了结构性和逻辑性重复 记录了所有重复出现的位置 抽象阶段 清楚区分了可变和不变部分 选择了合适的抽象形式(函数/类/配置) 抽象有清晰、描述性的命名 参数数量合理(≤ 4 个) 没有过度抽象 替换阶段 所有重复点都已替换 没有遗留旧代码 导入和依赖关系正确 处理了所有边缘情况 验证阶段 编写了单元测试 所有现有测试仍然通过 进行了手动验证(如适用) 性能没有显著下降 代码审查已完成(如在团队中工作) 整体质量 代码更易读、易维护 单一职责原则得到遵守 修改只需在一个地方进行 有充分的文档和注释 总结

DRY 原则是软件工程的基石之一。通过系统性地应用这个四步流程,你可以:

识别重复:培养对代码坏味道的敏感度 抽象逻辑:创建可复用、易维护的代码单元 替换实现:消除重复,统一实现 验证测试:确保重构的安全性

记住:

不要过早抽象(等到有明确重复时再抽象) 不要过度抽象(保持简单明了) 小步前进(渐进式重构比一次性大重构更安全) 测试是你的安全网(重构前先写测试)

最终目标:

让每一处知识在系统中都有唯一的、权威的表示。当需要修改时,你只改一个地方,所有使用该知识的地方自动更新。

这就是 DRY 的力量。

返回排行榜