dry-refactoring
npx skills add https://github.com/yyh211/claude-meta-skill --skill dry-refactoring
Agent 安装分布
Skill 文档
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<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 (éå¤ä»£ç )ï¼
# ä½ç½® 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<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):
# 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<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. æ¸è¿å¼éæ
ä¸è¦è¯å¾ä¸æ¬¡æ§éææ´ä¸ªä»£ç åºï¼
çç¥ï¼
- â æ¯æ¬¡éæä¸ä¸ªå°çéå¤åºå
- â éæåç«å³æµè¯
- â æäº¤å°çãååæ§çåæ´
- â 鿥æ©å¤§éæèå´
示ä¾å·¥ä½æµï¼
# 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. ç»§ç»å
¶ä»æ¨¡å...
2. ææ¡£åä½ çæ½è±¡
å¥½çæ½è±¡éè¦å¥½çææ¡£ï¼
/**
* 计ç®ååææ£åçä»·æ ¼
*
* @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);
}
3. 使ç¨ç±»åç³»ç»
å©ç¨ç±»åç³»ç»é²æ¢è¯¯ç¨ï¼
// 使ç¨ç±»åå«åå¢å¼ºå¯è¯»æ§
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
4. éæåå åæµè¯
å¦ææ²¡ææµè¯ï¼å åæµè¯åéæï¼
// æ¥éª¤ 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 çåéã