三、重构的时机与方法
3.1 什么时候应该重构?
重构的信号(代码坏味):
重复代码:相同的代码出现在多个地方
过长函数:一个函数超过50行
过大类:一个类做了太多事情
过长参数列表:超过3个参数
发散式变化:一个类因为多种原因被修改
霰弹式修改:一个变化需要修改多个类
依恋情结:一个函数过度使用其他类的数据
数据泥团:多个数据项总是同时出现
基本类型偏执:使用基本类型代替小对象
switch/if链:大量的条件判断
# 坏味示例1:重复代码
# ❌ 重复代码
def process_order(order):
total = 0
for item in order.items:
total += item.price * item.quantity
if total > 100:
total = total * 0.9
return total
def process_cart(cart):
total = 0
for item in cart.items:
total += item.price * item.quantity
if total > 100:
total = total * 0.9
return total
# ✅ 提取公共方法
def calculate_total_with_discount(items: List[Item]) -> Decimal:
total = sum(item.price * item.quantity for item in items)
if total > 100:
total = total * 0.9
return total
def process_order(order):
return calculate_total_with_discount(order.items)
def process_cart(cart):
return calculate_total_with_discount(cart.items)
# 坏味示例2:过长参数列表
# ❌ 过长参数
def create_user(name, email, phone, address, city, state, zip_code, country):
pass
# ✅ 引入参数对象
@dataclass
class UserInfo:
name: str
email: str
phone: str
address: Address
@dataclass
class Address:
street: str
city: str
state: str
zip_code: str
country: str
def create_user(user_info: UserInfo):
pass
# 坏味示例3:switch/if链
# ❌ 大量if-else
def calculate_discount(user_type, amount):
if user_type == "normal":
if amount > 100:
return amount * 0.05
else:
return 0
elif user_type == "vip":
if amount > 100:
return amount * 0.15
elif amount > 50:
return amount * 0.1
else:
return amount * 0.05
elif user_type == "svip":
return amount * 0.25
elif user_type == "employee":
return amount * 0.3
else:
return 0
# ✅ 使用策略模式+规则引擎
from abc import ABC, abstractmethod
from typing import List
class DiscountRule(ABC):
@abstractmethod
def calculate(self, amount: Decimal) -> Decimal:
pass
class NormalUserDiscount(DiscountRule):
def calculate(self, amount: Decimal) -> Decimal:
return amount * Decimal('0.05') if amount > 100 else Decimal('0')
class VIPUserDiscount(DiscountRule):
def calculate(self, amount: Decimal) -> Decimal:
if amount > 100:
return amount * Decimal('0.15')
elif amount > 50:
return amount * Decimal('0.1')
return amount * Decimal('0.05')
class SVIPUserDiscount(DiscountRule):
def calculate(self, amount: Decimal) -> Decimal:
return amount * Decimal('0.25')
class EmployeeDiscount(DiscountRule):
def calculate(self, amount: Decimal) -> Decimal:
return amount * Decimal('0.3')
class DiscountCalculator:
_rules = {
"normal": NormalUserDiscount(),
"vip": VIPUserDiscount(),
"svip": SVIPUserDiscount(),
"employee": EmployeeDiscount()
}
@classmethod
def calculate(cls, user_type: str, amount: Decimal) -> Decimal:
rule = cls._rules.get(user_type)
if not rule:
return Decimal('0')
return rule.calculate(amount)
3.2 经典重构手法
3.2.1 提取方法(Extract Method)
# 重构前
def print_bill(customer):
print(f"Customer: {customer.name}")
print(f"Date: {datetime.now()}")
total = 0
for item in customer.orders:
print(f" {item.name}: ${item.price}")
total += item.price
print(f"Total: ${total}")
if total > 100:
discount = total * 0.1
print(f"Discount: ${discount}")
print(f"Final: ${total - discount}")
# 重构后
def print_bill(customer):
print_header(customer)
print_items(customer.orders)
print_summary(customer.orders)
def print_header(customer):
print(f"Customer: {customer.name}")
print(f"Date: {datetime.now()}")
def print_items(orders):
for item in orders:
print(f" {item.name}: ${item.price}")
def print_summary(orders):
total = sum(item.price for item in orders)
print(f"Total: ${total}")
if total > 100:
discount = total * 0.1
print(f"Discount: ${discount}")
print(f"Final: ${total - discount}")
3.2.2 内联方法(Inline Method)
# 重构前
def get_rating(driver):
return more_than_five_late_deliveries(driver) ? 2 : 1
def more_than_five_late_deliveries(driver):
return driver.number_of_late_deliveries > 5
# 重构后(函数太简单,直接内联)
def get_rating(driver):
return 2 if driver.number_of_late_deliveries > 5 else 1
3.2.3 移动方法(Move Method)
# 重构前 - 方法放错了地方
class Account:
def __init__(self, account_type, days_overdrawn):
self.type = account_type
self.days_overdrawn = days_overdrawn
def bank_charge(self):
result = 4.5
if self.days_overdrawn > 0:
result += self.overdraft_charge()
return result
def overdraft_charge(self):
if self.type.is_premium:
base_charge = 10
if self.days_overdrawn <= 7:
return base_charge
else:
return base_charge + (self.days_overdrawn - 7) * 0.85
else:
return self.days_overdrawn * 1.75
# 重构后 - 移动到合适的类
class AccountType:
def overdraft_charge(self, days_overdrawn):
if self.is_premium:
base_charge = 10
if days_overdrawn <= 7:
return base_charge
else:
return base_charge + (days_overdrawn - 7) * 0.85
else:
return days_overdrawn * 1.75
class Account:
def __init__(self, account_type, days_overdrawn):
self.type = account_type
self.days_overdrawn = days_overdrawn
def bank_charge(self):
result = 4.5
if self.days_overdrawn > 0:
result += self.type.overdraft_charge(self.days_overdrawn)
return result
3.2.4 以多态取代条件表达式(Replace Conditional with Polymorphism)
# 重构前
class Bird:
def __init__(self, name, type, number_of_coconuts=0, voltage=0, is_nailed=False):
self.name = name
self.type = type
self.number_of_coconuts = number_of_coconuts
self.voltage = voltage
self.is_nailed = is_nailed
def plumage(self):
if self.type == "EuropeanSwallow":
return "average"
elif self.type == "AfricanSwallow":
if self.number_of_coconuts > 2:
return "tired"
else:
return "average"
elif self.type == "NorwegianBlueParrot":
if self.voltage > 100:
return "scorched"
else:
return "beautiful"
else:
return "unknown"
def air_speed_velocity(self):
if self.type == "EuropeanSwallow":
return 35
elif self.type == "AfricanSwallow":
return 40 - 2 * self.number_of_coconuts
elif self.type == "NorwegianBlueParrot":
return 0 if self.is_nailed else 10 + self.voltage / 10
else:
return None
# 重构后
from abc import ABC, abstractmethod
class Bird(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def plumage(self):
pass
@abstractmethod
def air_speed_velocity(self):
pass
class EuropeanSwallow(Bird):
def plumage(self):
return "average"
def air_speed_velocity(self):
return 35
class AfricanSwallow(Bird):
def __init__(self, name, number_of_coconuts):
super().__init__(name)
self.number_of_coconuts = number_of_coconuts
def plumage(self):
return "tired" if self.number_of_coconuts > 2 else "average"
def air_speed_velocity(self):
return 40 - 2 * self.number_of_coconuts
class NorwegianBlueParrot(Bird):
def __init__(self, name, voltage, is_nailed):
super().__init__(name)
self.voltage = voltage
self.is_nailed = is_nailed
def plumage(self):
return "scorched" if self.voltage > 100 else "beautiful"
def air_speed_velocity(self):
return 0 if self.is_nailed else 10 + self.voltage / 10
# 工厂方法创建鸟对象
def create_bird(bird_data):
if bird_data["type"] == "EuropeanSwallow":
return EuropeanSwallow(bird_data["name"])
elif bird_data["type"] == "AfricanSwallow":
return AfricanSwallow(bird_data["name"], bird_data["number_of_coconuts"])
elif bird_data["type"] == "NorwegianBlueParrot":
return NorwegianBlueParrot(bird_data["name"], bird_data["voltage"], bird_data["is_nailed"])
else:
raise ValueError(f"Unknown bird type: {bird_data['type']}")
3.2.5 引入空对象(Introduce Null Object)
# 重构前
class Customer:
def get_name(self):
return self.name
def get_plan(self):
return self.plan
def get_customer_name(customer):
if customer is None:
return "Unknown"
return customer.get_name()
# 重构后
class NullCustomer(Customer):
def get_name(self):
return "Unknown"
def get_plan(self):
return NullPlan()
def is_null(self):
return True
class RealCustomer(Customer):
def get_name(self):
return self.name
def get_plan(self):
return self.plan
def is_null(self):
return False
def get_customer_name(customer):
# 不需要判空,空对象自然返回"Unknown"
return customer.get_name()
# 使用
customer = find_customer(id) or NullCustomer()
name = customer.get_name() # 安全,不需要判空
3.3 重构的步骤与策略
# 重构的六步法
"""
第一步:确保有足够的测试覆盖
- 重构前先写测试
- 确保测试通过
- 每次小的重构后运行测试
第二步:识别重构目标
- 找出代码坏味
- 确定重构手法
- 评估重构收益
第三步:小步前进
- 每次只做一个重构
- 每步后运行测试
- 提交到版本控制
第四步:持续验证
- 运行所有测试
- 检查性能影响
- Code Review
第五步:清理临时变量
- 删除无用代码
- 更新注释
- 格式化代码
第六步:提交并文档化
- 清晰的commit message
- 更新相关文档
- 通知团队
"""
# 重构示例:逐步改进
# 起始代码(需要重构)
def calculate_order_total(order):
total = 0
for i in range(len(order['items'])):
item = order['items'][i]
if item['type'] == 'physical':
price = item['price']
quantity = item['quantity']
tax = price * 0.1
total += (price + tax) * quantity
elif item['type'] == 'digital':
price = item['price']
quantity = item['quantity']
total += price * quantity
elif item['type'] == 'gift_card':
price = item['price']
total += price
if order.get('coupon'):
if order['coupon']['type'] == 'percentage':
total = total * (1 - order['coupon']['value'] / 100)
elif order['coupon']['type'] == 'fixed':
total = total - order['coupon']['value']
if total < 0:
total = 0
return total
# 第一步:提取商品价格计算逻辑
def calculate_item_total(item):
if item['type'] == 'physical':
price = item['price']
quantity = item['quantity']
tax = price * 0.1
return (price + tax) * quantity
elif item['type'] == 'digital':
return item['price'] * item['quantity']
elif item['type'] == 'gift_card':
return item['price']
else:
return 0
def calculate_order_total(order):
total = sum(calculate_item_total(item) for item in order['items'])
if order.get('coupon'):
total = apply_coupon(total, order['coupon'])
return max(total, 0)
# 第二步:使用多态替代类型判断
from abc import ABC, abstractmethod
class Item(ABC):
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
@abstractmethod
def calculate_total(self):
pass
class PhysicalItem(Item):
def calculate_total(self):
tax = self.price * 0.1
return (self.price + tax) * self.quantity
class DigitalItem(Item):
def calculate_total(self):
return self.price * self.quantity
class GiftCardItem(Item):
def calculate_total(self):
return self.price
# 第三步:提取优惠计算策略
class CouponStrategy(ABC):
@abstractmethod
def apply(self, total):
pass
class PercentageCoupon(CouponStrategy):
def __init__(self, percentage):
self.percentage = percentage
def apply(self, total):
return total * (1 - self.percentage / 100)
class FixedAmountCoupon(CouponStrategy):
def __init__(self, amount):
self.amount = amount
def apply(self, total):
return max(total - self.amount, 0)
def apply_coupon(total, coupon_data):
if coupon_data['type'] == 'percentage':
strategy = PercentageCoupon(coupon_data['value'])
elif coupon_data['type'] == 'fixed':
strategy = FixedAmountCoupon(coupon_data['value'])
else:
return total
return strategy.apply(total)
# 最终重构后的代码
class OrderCalculator:
def calculate_total(self, order):
items_total = sum(item.calculate_total() for item in order.items)
final_total = self._apply_coupon(items_total, order.coupon)
return max(final_total, 0)
def _apply_coupon(self, total, coupon):
if not coupon:
return total
strategy = self._create_coupon_strategy(coupon)
return strategy.apply(total)
def _create_coupon_strategy(self, coupon):
strategies = {
'percentage': lambda c: PercentageCoupon(c['value']),
'fixed': lambda c: FixedAmountCoupon(c['value'])
}
creator = strategies.get(coupon.type)
if not creator:
raise ValueError(f"Unknown coupon type: {coupon.type}")
return creator(coupon)