原文链接:https://www.arjancodes.com/blog/best-practices-for-eliminating-python-code-smells/
文章列举了多种糟糕的代码模式,并给出了解决方法。通过这些修改,可以使得代码更易读、更可维护。 这些糟糕的代码气味是:
any
, all
处理多个条件。class OnlineStore:
def search_product(self, query: Query):
# Logic to search for products in some database
pass
def process_order(self, order: Order):
# Logic to process the order and send confirmation email
pass
def handle_payment(self, payment_info: PaymentInfo):
# Logic to handle payment and update the order status
pass
def manage_inventory(self, product_id: int, quantity: int):
# Logic to manage inventory and update the database
pass
# Many more methods
“上帝对象”是整体设计的,处理了太多的任务和责任,违反了SOLID设计的单一责任原则(SRP, Single Responsibility priciple)。代码示例中的 OnlineStore
类负责库存管理、订单处理、付款接受和产品搜索。将所有这些职责合并到一个类别中可能会限制我们引入新功能的灵活性,同时增加测试和维护的复杂性。
我们可以将 OnlineStore 类重写为更易于管理的专用类(如 ProductSearch 、 OrderProcessor 、 PaymentGateway 和 InventoryManager )。这使得每个类在遵守 SRP 的同时专注于特定任务。
class ProductSearch:
def search_product(self, query: Query):
# Logic to search for products in some database
pass
class OrderProcessor:
def process_order(self, order: Order):
# Logic to process the order and send confirmation email
pass
class PaymentGateway:
def handle_payment(self, total: int, payment_info: Payment):
# Logic to handle payment and update the order status
pass
class InventoryManager:
def manage_inventory(self, product_id: int, quantity: int):
# Logic to manage inventory and update the database
pass
class ReportGenerator:
def generate_sales_report(self, sales_data: list[Report):
# Preprocessing steps, such as formatting the data into a table.
# Generate sales report
pass
def generate_inventory_report(self, inventory_data: list[Report]):
# Preprocessing steps (duplicated)
# Generate inventory report
pass
当相同的代码块多次出现时,它被视为重复代码。重复代码增加了冗余和不一致的可能。 我们可以将这些重复的过程组合成一个单一的方法来解决这个问题。通过这种方式,我们消除了冗余,并将其与 DRY(不要重复自己)编码理念保持一致。
class ReportGenerator:
def preprocess_data(self, data: list[Report]):
# Common preprocessing steps
pass
def generate_sales_report(self, sales_data: list[Report]):
self.preprocess_data(sales_data)
# Generate sales report
pass
def generate_inventory_report(self, inventory_data: list[Report]):
self.preprocess_data(inventory_data)
# Generate inventory report
pass
def handle_customer_request(request: CustomerRequest):
# Validate request
# Log request details
# Check inventory
# Calculate pricing
# Apply discounts
# Finalize response
pass
“长方法”包含太多的代码行,并且通常难以阅读、理解和测试。
通过将此方法分解为更小、更集中的函数,可以提高可读性和可重用性。通过将方法分离成更小、更集中的函数,我们可以提高可读性和可重用性,并简化单元测试。我们应该致力于使每种方法都负责一项单一的任务。
def handle_customer_request(request: CustomerRequest):
validate_request(request)
log_request(request)
check_inventory(request)
pricing = calculate_pricing(request)
apply_discounts(pricing)
return finalize_response(pricing)
def validate_request(request: Request): pass
def log_request(request: Request): pass
def check_inventory(request: Request): pass
def calculate_pricing(request: Request): pass
def apply_discounts(pricing: int): pass
def finalize_response(pricing: int): pass
def calculate_shipping_cost(distance: float) -> float:
return distance * 1.25 # What does 1.25 signify?
“幻数”是那些棘手的数字文字,经常出现在编程代码中,没有明显的解释,使代码更难理解和处理。该 calculate_shipping_cost 函数在没有任何上下文的情况下使用数字 1.25,让我们猜测它的目的和含义。 相反,我们可以引入一个名为 PER_MILE_SHIPPING_RATE 的常量,它清楚地表明 1.25 表示每英里的运输成本。这个简单的更改使我们的代码更易于理解,也简化了将来对此值的更改。
PER_MILE_SHIPPING_RATE = 1.25
def calculate_shipping_cost(distance: float) -> float:
return distance * PER_MILE_SHIPPING_RATE
def approve_loan(application: LoanApplication) -> bool:
if application.credit_score > 600:
if application.income > 30000:
if application.debt_to_income_ratio < 0.4:
return True
else:
return False
else:
return False
else:
return False
嵌套的条件语句可能会使理解函数的流变得困难。该 approve_loan 方法被一系列难以理解的嵌套 if 语句包围。
通过重构我们的代码,以便按顺序检查每个条件,我们可以创建一个更扁平、更易于阅读和理解的结构。如果将复杂的逻辑与条件混合在一起,则可能值得将逻辑抽象为单独的函数,以使条件更易于阅读。如果您有一系列需要满足的条件,请考虑使用 any 和 all 内置函数来使条件更具可读性。
def approve_loan(application: LoanApplication) -> bool:
if application.credit_score <= 600:
return False
if application.income <= 30000:
return False
if application.debt_to_income_ratio >= 0.4:
return False
return True
或者使用 any/all 内置函数:
def approve_loan(application: LoanApplication) -> bool:
return all([
application.credit_score > 600,
application.income > 30000,
application.debt_to_income_ratio < 0.4
])