| name | dry-refactoring |
| description | Guides systematic code refactoring following the DRY (Don't Repeat Yourself) principle. Use when user asks to eliminate code duplication, refactor repetitive code, apply DRY principle, or mentions code smells like copy-paste, magic numbers, or repeated logic. Implements a 4-step workflow from identifying repetition to verified refactoring. |
DRY 标准化流程:从识别到重构
这个技能指导你系统性地应用 DRY (Don't Repeat Yourself) 原则,通过四步标准化流程消除代码重复,提升代码质量和可维护性。
When to Use This Skill
使用此技能当用户请求:
- 消除代码重复或冗余
- 重构有明显复制粘贴痕迹的代码
- 应用 DRY 原则优化代码库
- 识别并修复"代码坏味道"(如魔术数字、重复逻辑)
- 提取公共逻辑为可复用单元
- 改善代码的可维护性
关键触发词: DRY, 重复代码, 代码重复, 重构, 消除重复, 复制粘贴, 魔术数字, 代码坏味道, 抽象, 提取函数
核心思想
系统中的每一处知识都必须拥有一个单一、明确、权威的表示。
这意味着:
- 任何业务逻辑、算法或配置信息都应该只存在于代码库的一个地方
- 如果需要修改,你只需改这一个地方
- 修改会自动反映到所有使用该逻辑的地方
两次法则 (Rule of Two):
当你第二次写下几乎相同的代码块时,警钟就应该敲响。这是开始重构的信号。
四步标准化流程
这是一个可在编码任何阶段应用的微循环。严格按照步骤执行,确保重构的安全性和有效性。
第一步:识别重复 (Identify Repetition)
目标: 像侦探一样,对代码中的"坏味道"保持警惕,找出所有重复。
1.1 明显的重复
直接复制粘贴:
- 两块或多块代码长得几乎一模一样
- 只有变量名或少数值不同
- 这是最明显、最需要被消除的重复
示例:
function calculateOrderDiscount(orderTotal) {
if (orderTotal > 100) {
return orderTotal * 0.1;
}
return 0;
}
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));
}
识别清单
当你审查代码时,检查以下信号:
💡 Action: 使用搜索功能查找重复的字面量、相似的函数名模式。记录所有重复出现的位置。
第二步:抽象逻辑 (Abstract the Logic)
目标: 将重复的逻辑提取出来,封装到一个独立、可复用的单元中。
2.1 识别可变与不变部分
不变部分:
- 这是重复的核心逻辑
- 每次重复时都保持不变的代码
- 这将成为你的抽象主体
可变部分:
- 每次重复时发生变化的东西
- 不同的值、变量名、配置
- 这些将成为函数或类的参数
分析示例:
const userEmail = validateEmail(user.email);
const adminEmail = validateEmail(admin.email);
const supportEmail = validateEmail(support.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)
def process_user_payment(user_id, payment_data):
user = get_user_or_error(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<T> {
private store = new Map<string, T>();
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<User>();
const productCache = new Cache<Product>();
抽象设计原则
Do:
- ✅ 参数化可变部分(值、配置、行为)
- ✅ 保持接口简单(参数数量 ≤ 4 个)
- ✅ 使用描述性命名(说明"做什么"而非"怎么做")
- ✅ 考虑未来的扩展性(但不要过度设计)
Don't:
- ❌ 创建过于通用的抽象("万能函数")
- ❌ 过早抽象(只有一次使用时不要抽象)
- ❌ 忽略性能影响(例如不必要的函数调用开销)
- ❌ 使用难以理解的抽象(增加认知负担)
💡 Action: 创建一个新的函数、类或配置文件,将"不变部分"放进去,将"可变部分"定义为参数。
第三步:替换实现 (Replace the Implementation)
目标: 用新的抽象单元替换所有旧的重复代码。
3.1 系统性替换
步骤:
- 定位所有重复点:回到第一步记录的所有位置
- 逐一替换:删除旧代码,调用新抽象
- 传入正确参数:确保参数对应正确
- 保持行为一致:确保替换前后功能完全相同
替换示例:
Before (重复代码):
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
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 (使用抽象):
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
def create_user(data):
error = validate_required_fields(data, ['email', 'password'])
if error:
return error
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<T>(
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):
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 手动验证
如果没有自动化测试(或测试覆盖不足),进行手动验证:
验证清单:
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: 运行所有相关测试,确保程序的外部行为在重构前后完全一致。没有测试?现在是编写测试的最佳时机。
完整示例:从头到尾
场景:电商系统的折扣计算
原始代码(存在重复)
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;
}
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:抽象逻辑
function calculateItemsTotal(items) {
return items.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
}
const DISCOUNT_RATES = {
membership: {
gold: 0.2,
silver: 0.1,
bronze: 0.05
},
coupon: {
premium: 0.2,
standard: 0.1,
basic: 0.05
}
};
function applyDiscount(amount, discountRate) {
if (discountRate < 0 || discountRate > 1) {
throw new Error('Invalid discount rate');
}
return amount * (1 - discountRate);
}
function getDiscountRate(category, level) {
return DISCOUNT_RATES[category]?.[level] || 0;
}
export { calculateItemsTotal, applyDiscount, getDiscountRate };
步骤 3:替换实现
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);
}
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:验证与测试
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);
});
});
});
重构成果:
- ✅ 消除了所有重复代码
- ✅ 魔术数字集中管理
- ✅ 每个函数职责单一
- ✅ 易于测试和维护
- ✅ 如果需要添加新的会员等级或折扣类型,只需修改
DISCOUNT_RATES
常见陷阱与解决方案
陷阱 1:过度抽象 (Over-Abstraction)
症状:创建了过于通用、难以理解的抽象。
示例:
function universalProcessor(data, options, callbacks, config, meta) {
}
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<string, User>;
getUser(id: string) {
return this.users.get(id);
}
}
class UserManager {
private users: Map<string, User>;
getUser(id: string): User | undefined {
return this.users.get(id);
}
addUser(user: User): void {
this.users.set(user.id, user);
}
}
解决方案:
- 使用访问控制(private, protected)
- 提供明确的公共接口
- 隐藏实现细节
最佳实践
1. 渐进式重构
不要试图一次性重构整个代码库:
策略:
- ✅ 每次重构一个小的重复区域
- ✅ 重构后立即测试
- ✅ 提交小的、原子性的变更
- ✅ 逐步扩大重构范围
示例工作流:
git checkout -b refactor/dry-pricing-logic
npm test
git add pricing_utils.js
git commit -m "Extract pricing calculation to reusable utility"
npm test
git add order_service.js
git commit -m "Refactor order service to use pricing utility"
2. 文档化你的抽象
好的抽象需要好的文档:
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);
}
3. 使用类型系统
利用类型系统防止误用:
type DiscountRate = number;
type Price = number;
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);
4. 重构前先写测试
如果没有测试,先写测试再重构:
describe('Original Implementation', () => {
it('should calculate order total correctly', () => {
const order = {
items: [{ price: 10, quantity: 2 }],
memberLevel: 'gold'
};
expect(calculateOrderTotal(order)).toBe(16);
});
});
检查清单
在完成 DRY 重构后,验证以下内容:
识别阶段
抽象阶段
替换阶段
验证阶段
整体质量
总结
DRY 原则是软件工程的基石之一。通过系统性地应用这个四步流程,你可以:
- 识别重复:培养对代码坏味道的敏感度
- 抽象逻辑:创建可复用、易维护的代码单元
- 替换实现:消除重复,统一实现
- 验证测试:确保重构的安全性
记住:
- 不要过早抽象(等到有明确重复时再抽象)
- 不要过度抽象(保持简单明了)
- 小步前进(渐进式重构比一次性大重构更安全)
- 测试是你的安全网(重构前先写测试)
最终目标:
让每一处知识在系统中都有唯一的、权威的表示。当需要修改时,你只改一个地方,所有使用该知识的地方自动更新。
这就是 DRY 的力量。