使用有效的重构技术,提高您的 Python 代码的可维护性、可读性和性能。学习改进代码质量的实用策略和最佳实践。
Python 重构技术:代码质量改进综合指南
在不断发展的软件开发领域,保持清晰、高效和易于理解的代码至关重要。 Python 以其可读性而闻名,但如果管理不当,仍然容易受到代码异味和技术债务的困扰。重构是重组现有计算机代码的过程——更改结构——而不改变其外部行为。 简而言之,它是在不破坏代码的情况下清理代码。 本指南探讨了各种 Python 重构技术,提供了实际示例和最佳实践,以提高您的代码质量。
为什么重构 Python 代码?
重构提供了许多好处,包括:
- 提高可读性: 使代码更容易理解和维护。
- 降低复杂性: 简化复杂的逻辑,降低出错的可能性。
- 增强可维护性: 方便更容易地修改和扩展代码。
- 提高性能: 可以优化代码以获得更好的执行速度。
- 降低技术债务: 避免积累难以维护或扩展的代码。
- 更好的设计: 带来更稳健和灵活的代码架构。
忽略重构会导致代码难以理解、修改和测试。 这可能会显着增加开发时间和引入错误的风险。
什么时候重构?
知道何时重构至关重要。 以下是一些常见场景:
- 在添加新功能之前: 重构现有代码可以更容易地集成新功能。
- 修复错误后: 重构周围的代码可以防止出现类似的错误。
- 在代码审查期间: 确定可以改进的领域并对其进行重构。
- 当您遇到“代码异味”时: 代码异味是代码中潜在问题的表面迹象。
- 定期安排重构: 将重构作为日常活动纳入您的开发流程。
识别代码异味
代码异味是通常对应于系统中更深层问题的表面指示。 它们并不总是表明存在问题,但它们通常值得进一步调查。
常见的 Python 代码异味:
- 重复代码: 在多个地方出现相同或非常相似的代码。
- 长方法/函数: 方法或函数过长且复杂。
- 大类: 具有太多职责的类。
- 长参数列表: 具有太多参数的方法或函数。
- 数据团: 经常一起出现的数据组。
- 原始痴迷: 使用原始数据类型而不是创建对象。
- Switch 语句: 长链 if/elif/else 语句或 switch 语句。
- 霰弹式手术: 进行一次更改需要对不同的类进行许多小的更改。
- 发散变化: 为了不同的原因,一个类通常以不同的方式更改。
- 功能嫉妒: 一个方法访问另一个对象的数据多于其自身的数据。
- 消息链: 客户端要求一个对象请求另一个对象请求另一个对象...
Python 重构技术:实践指南
本节详细介绍了几个常见的 Python 重构技术,并提供了实际示例。
1. 提取方法/函数
此技术涉及将方法或函数中的代码块移动到新的单独的方法或函数中。 这降低了原始方法的复杂性,并使提取的代码可重用。
示例:
def print_invoice(customer, details):
print("***********************")
print(f"Customer: {customer}")
print("***********************")
total_amount = 0
for order in details["orders"]:
total_amount += order["amount"]
print(f"Amount is : {total_amount}")
if total_amount > 1000:
print("You earned a discount!")
重构后:
def print_header(customer):
print("***********************")
print(f"Customer: {customer}")
print("***********************")
def calculate_total(details):
total_amount = 0
for order in details["orders"]:
total_amount += order["amount"]
return total_amount
def print_invoice(customer, details):
print_header(customer)
total_amount = calculate_total(details)
print(f"Amount is : {total_amount}")
if total_amount > 1000:
print("You earned a discount!")
2. 提取类
当一个类有太多职责时,将其中的一些职责提取到一个新类中。 这促进了单一职责原则。
示例:
class Person:
def __init__(self, name, phone_number, office_area_code, office_number):
self.name = name
self.phone_number = phone_number
self.office_area_code = office_area_code
self.office_number = office_number
def get_name(self):
return self.name
def get_phone_number(self):
return f"({self.office_area_code}) {self.office_number}"
重构后:
class PhoneNumber:
def __init__(self, area_code, number):
self.area_code = area_code
self.number = number
def get_phone_number(self):
return f"({self.area_code}) {self.number}"
class Person:
def __init__(self, name, phone_number):
self.name = name
self.phone_number = phone_number
def get_name(self):
return self.name
3. 内联方法/函数
这与提取方法相反。 如果一个方法的主体与其名称一样清晰,您可以通过使用该方法的内容替换对该方法的调用来内联该方法。
示例:
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 driver.number_of_late_deliveries > 5 ? 2 : 1
4. 用查询替换临时变量
与其使用临时变量来保存表达式的结果,不如将表达式提取到一个方法中。 这避免了代码重复并提高了可读性。
示例:
def get_price(order):
base_price = order.quantity * order.item_price
discount_factor = 0.98 if base_price > 1000 else 0.95
return base_price * discount_factor
重构后:
def get_price(order):
return base_price(order) * discount_factor(order)
def base_price(order):
return order.quantity * order.item_price
def discount_factor(order):
return 0.98 if base_price(order) > 1000 else 0.95
5. 引入参数对象
如果您有一长串经常一起出现的参数,请考虑创建一个参数对象来封装它们。 这减少了参数列表的长度,并改善了代码组织。
示例:
def calculate_total(width, height, depth, weight, shipping_method):
# Calculation logic
pass
重构后:
class ShippingDetails:
def __init__(self, width, height, depth, weight, shipping_method):
self.width = width
self.height = height
self.depth = depth
self.weight = weight
self.shipping_method = shipping_method
def calculate_total(shipping_details):
# Calculation logic using shipping_details attributes
pass
6. 用多态替换条件
当您有一个复杂的条件语句,该语句根据对象的类型选择行为时,请考虑使用多态将该行为委托给子类。 这促进了更好的代码组织,并使其更容易添加新类型。
示例:
def calculate_bonus(employee):
if employee.employee_type == "SALES":
return employee.sales * 0.1
elif employee.employee_type == "ENGINEER":
return employee.projects_completed * 100
elif employee.employee_type == "MANAGER":
return 1000
else:
return 0
重构后:
class Employee:
def calculate_bonus(self):
return 0
class SalesEmployee(Employee):
def __init__(self, sales):
self.sales = sales
def calculate_bonus(self):
return self.sales * 0.1
class EngineerEmployee(Employee):
def __init__(self, projects_completed):
self.projects_completed = projects_completed
def calculate_bonus(self):
return self.projects_completed * 100
class ManagerEmployee(Employee):
def calculate_bonus(self):
return 1000
7. 分解条件
与提取方法类似,这涉及将复杂的条件语句分解为更小、更易于管理的方法。 这提高了可读性,并使理解条件的逻辑变得更容易。
示例:
if (platform.upper().index("MAC") > -1) and (browser.upper().index("IE") > -1) and was_initialized() and resize > MAX_RESIZE:
# Do something
pass
重构后:
def is_mac_os():
return platform.upper().index("MAC") > -1
def is_ie_browser():
return browser.upper().index("IE") > -1
if is_mac_os() and is_ie_browser() and was_initialized() and resize > MAX_RESIZE:
# Do something
pass
8. 用符号常量替换魔术数字
用命名的常量替换字面数值。 这提高了可读性,并且更容易在以后更改这些值。 这也适用于其他文字值,例如字符串。 考虑货币代码(例如 'USD'、'EUR'、'JPY')或状态代码(例如 'ACTIVE'、'INACTIVE'、'PENDING')的全局角度。
示例:
def calculate_area(radius):
return 3.14159 * radius * radius
重构后:
PI = 3.14159
def calculate_area(radius):
return PI * radius * radius
9. 删除中间人
如果一个类只是将调用委托给另一个类,请考虑删除中间人,并允许客户端直接访问目标类。
示例:
class Person:
def __init__(self, department):
self.department = department
def get_manager(self):
return self.department.get_manager()
class Department:
def __init__(self, manager):
self.manager = manager
def get_manager(self):
return self.manager
重构后:
class Person:
def __init__(self, manager):
self.manager = manager
def get_manager(self):
return self.manager
10. 引入断言
使用断言来记录关于程序状态的假设。 这可以帮助及早发现错误并使代码更健壮。
示例:
def calculate_discount(price, discount_percentage):
if discount_percentage < 0 or discount_percentage > 100:
raise ValueError("Discount percentage must be between 0 and 100")
return price * (1 - discount_percentage / 100)
重构后:
def calculate_discount(price, discount_percentage):
assert 0 <= discount_percentage <= 100, "Discount percentage must be between 0 and 100"
return price * (1 - discount_percentage / 100)
Python 重构工具
有几种工具可以帮助进行 Python 重构:
- Rope: Python 的重构库。
- PyCharm: 具有内置重构支持的流行的 Python IDE。
- 带有 Python 扩展的 VS Code: 一种多功能编辑器,通过扩展提供重构功能。
- Sourcery: 一种自动重构工具。
- Bowler: Facebook 用于大规模代码修改的重构工具。
Python 重构的最佳实践
- 编写单元测试: 在重构之前确保您的代码经过充分测试。
- 以小步骤重构: 进行小的、增量的更改,以最大限度地减少引入错误的风险。
- 在每个重构步骤后进行测试: 验证您的更改没有破坏任何内容。
- 使用版本控制: 经常提交您的更改,以便在必要时轻松恢复。
- 与您的团队沟通: 让您的团队了解您的重构计划。
- 注重可读性: 优先考虑使您的代码更容易理解。
- 不要仅仅为了重构而重构: 在解决特定问题时进行重构。
- 尽可能自动化重构: 利用工具来自动化重复的重构任务。
重构的全局注意事项
在从事国际项目或面向全球受众时,请在重构期间考虑以下因素:
- 本地化 (L10n) 和国际化 (I18n): 确保您的代码正确支持不同的语言、货币和日期格式。 重构以隔离特定于语言环境的逻辑。
- 字符编码: 使用 UTF-8 编码来支持各种字符。 重构假定特定编码的代码。
- 文化敏感性: 注意文化规范,避免使用可能冒犯性的语言或图像。 在重构期间查看字符串文字和用户界面元素。
- 时区: 正确处理时区转换。 重构对用户时区做出假设的代码。 使用 `pytz` 等库。
- 货币处理: 使用适当的数据类型和库来处理货币值。 重构执行手动货币转换的代码。 `babel` 等库很有用。
示例:本地化日期格式
import datetime
def format_date(date):
return date.strftime("%m/%d/%Y") # US date format
重构后:
import datetime
import locale
def format_date(date, locale_code):
locale.setlocale(locale.LC_TIME, locale_code)
return date.strftime("%x") # Locale-specific date format
# Example usage:
# format_date(datetime.date(2024, 1, 1), 'en_US.UTF-8') # Output: '01/01/2024'
# format_date(datetime.date(2024, 1, 1), 'de_DE.UTF-8') # Output: '01.01.2024'
结论
重构是维护高质量 Python 代码的重要实践。 通过识别和解决代码异味、应用适当的重构技术并遵循最佳实践,您可以显着提高代码的可读性、可维护性和性能。 记住在整个重构过程中优先考虑测试和沟通。 将重构作为持续的过程来拥抱,将带来更稳健和可持续的软件开发工作流程,尤其是在为全球和多元化的受众开发时。