Рефакторинг кода — это процесс улучшения внутренней структуры программного кода без изменения его внешнего поведения. Основная цель — сделать код более понятным, простым в поддержке и расширении. В этой статье вместе с автором Stepik Гайк Инанц мы рассмотрим несколько подходов к рефакторингу на примерах кода на языке Python.
Представьте себе, что ваш код — это дом. Когда вы только начинаете его строить, вам нужно быстро возвести стены, крышу и окна, чтобы он начал функционировать. Вы можете не обращать внимание на детали — главное, чтобы дом стоял и был пригоден для жизни.
Но со временем вы замечаете, что некоторые элементы не так удобны. Например, мебель в неудобных местах, трубы проложены хаотично, а электрические розетки находятся только в одном углу. Жить в таком доме можно, но это неудобно и требует больше времени на выполнение простых задач.
Рефакторинг — это процесс "ремонта" вашего кода. Как если бы вы перестраивали дом, чтобы сделать его более удобным: переносили розетки в нужные места, перекладывали трубы и улучшали планировку. Дом после ремонта будет не только комфортнее для повседневной жизни, но и проще для последующих изменений. Если, например, вы захотите добавить новый этаж, у вас уже будет хорошая основа, к которой можно легко пристроить новые комнаты.
Так же и с кодом: рефакторинг улучшает его структуру, делая проще поддержание и добавление новых функций. Без рефакторинга код со временем станет "ветхим", сложным для изменений и увеличивающим риск появления ошибок — точно как старый дом, в котором постоянно возникают проблемы с коммуникациями и удобством.
Почему важен рефакторинг?
- Улучшение читаемости кода. Понятный код проще читать, что ускоряет процесс разработки и облегчает работу в команде.
- Упрощение поддержки. Хорошо структурированный код легче изменять и исправлять ошибки.
- Снижение технического долга. Регулярный рефакторинг помогает уменьшить количество устаревшего или сложного кода, который со временем становится проблемным.
- Повышение производительности. Оптимизация кода может ускорить выполнение программы.
Принципы хорошего рефакторинга
В процессе рефакторинга важно придерживаться ряда ключевых принципов, которые помогают улучшить читаемость и поддерживаемость кода. Давайте подробнее разберем три важных принципа: DRY, KISS и SRP, с примерами их применения на Python.
DRY (Donʼt Repeat Yourself) — избегайте дублирования кода.
DRY предполагает, что в коде не должно быть дублирующихся частей. Если один и тот же блок кода появляется в нескольких местах программы, его нужно вынести в отдельную функцию, метод или класс, который будет переиспользоваться. Дублирование кода увеличивает сложность поддержки и повышает риск возникновения ошибок: если потребуется изменить логику, придется править сразу в нескольких местах.
Пример нарушения принципа DRY
def calculate_discounted_price(price, discount):
return price - (price * discount / 100)
def calculate_final_price_with_tax(price, tax_rate):
Рефакторинг кода: Как улучшить читаемость и поддерживаемость вашего проекта 2
return price + (price * tax_rate / 100)
def calculate_total_price(price, discount, tax_rate):
discounted_price = price - (price * discount / 100)
final_price = discounted_price + (discounted_price * tax_
rate / 100)
return final_price
Здесь одна и та же логика расчета скидки и налога повторяется в нескольких местах. Если логика изменится (например, налог нужно будет считать по- другому), придется изменять код в трех местах, что повышает риск ошибок.
Исправление с соблюдением принципа DRY
def apply_percentage(value, percentage):
return value * percentage / 100
def calculate_discounted_price(price, discount):
return price - apply_percentage(price, discount)
def calculate_final_price_with_tax(price, tax_rate):
return price + apply_percentage(price, tax_rate)
def calculate_total_price(price, discount, tax_rate):
discounted_price = calculate_discounted_price(price, disc
ount)
return calculate_final_price_with_tax(discounted_price, t
ax_rate)
Теперь повторяющаяся логика вынесена в функцию apply_percentage , что улучшает читаемость и упрощает дальнейшие изменения.
KISS (Keep It Simple, Stupid) — не усложняйте архитектуру программы.
KISS означает, что код должен быть простым и не перегруженным ненужными сложностями. Чем проще код, тем легче его поддерживать и понимать. Часто разработчики склонны добавлять избыточные структуры или логики в код, что делает его сложнее и труднее для понимания. Принцип KISS призывает избегать такого усложнения и держать код максимально простым.
Пример нарушения принципа KISS
def calculate_area(shape, dimensions):
if shape == 'rectangle':
return dimensions['length'] * dimensions['width']
elif shape == 'triangle':
return 0.5 * dimensions['base'] * dimensions['heigh
t']
elif shape == 'circle':
import math
return math.pi * (dimensions['radius'] ** 2)
else:
return None
Здесь много логики в одной функции, и каждый раз, когда добавляется новая форма, нужно будет изменять функцию, что делает код сложнее.
Исправление с соблюдением принципа KISS
def calculate_rectangle_area(length, width):
return length * width
def calculate_triangle_area(base, height):
return 0.5 * base * height
def calculate_circle_area(radius):
import math
return math.pi * (radius ** 2)
Теперь каждая фигура имеет свою отдельную функцию для расчета площади. Это делает код проще и позволяет легче добавлять новые формы без изменения основной логики.
Single Responsibility Principle (SRP) — каждая функция или класс должен отвечать за одну задачу.
Принцип единственной ответственности гласит, что у каждой функции, класса или модуля должна быть четко определенная роль. Это упрощает их поддержку и тестирование. Если функция или класс выполняют слишком много задач, это затрудняет их использование, тестирование и изменяемость.
Пример нарушения принципа SRP
class Order:
def __init__(self, items):
self.items = items
def calculate_total(self):
total = sum(item['price'] for item in self.items)
return total
def print_receipt(self):
print("Receipt:")
for item in self.items:
print(f"{item['name']}: {item['price']}")
print(f"Total: {self.calculate_total()}")
Класс Order нарушает принцип SRP, так как отвечает за две задачи: расчет общей стоимости и печать квитанции.
Исправление с соблюдением принципа SRP
class Order:
def __init__(self, items):
self.items = items
def calculate_total(self):
return sum(item['price'] for item in self.items)
class ReceiptPrinter:
@staticmethod
def print_receipt(order):
print("Receipt:")
for item in order.items:
print(f"{item['name']}: {item['price']}")
print(f"Total: {order.calculate_total()}")
Теперь класс Order отвечает только за хранение данных о заказе и расчет его общей стоимости, а класс ReceiptPrinter отвечает за печать квитанции. Это улучшает разделение обязанностей и упрощает поддержку кода.
Применение принципов DRY, KISS и SRP делает код более читабельным, гибким и легким в поддержке. Они помогают избежать избыточности, усложнения и многозадачности в функциях и классах. Следование этим принципам улучшает качество кода и упрощает его масштабирование, что особенно важно при работе в команде или при долгосрочной поддержке проекта.
Дополнительные приемы и подходы для улучшения кода
Помимо ключевых принципов рефакторинга, таких как DRY, KISS и SRP, существуют другие практики, которые помогают сделать код более чистым, понятным и легким для поддержки. Рассмотрим некоторые из них на примерах.
Пример 1: Улучшение именования переменных и функций
Имена функций и переменных должны быть самодокументирующимися — то есть описывать свою роль в программе таким образом, чтобы код становился понятнее без необходимости дополнительных комментариев.
До рефакторинга:
def fn(lst):
for i in lst:
if i > 10:
print(i)
Имена функции fn и переменных lst и i не дают никакой информации о том, что делает этот код.
После рефакторинга:
def print_large_numbers(numbers):
for number in numbers:
if number > 10:
print(number)
Теперь функция и переменные имеют понятные имена, и любой разработчик сможет легко понять, что функция выводит числа, большие 10.
Пример 2: Упрощение сложных условий
Сложные логические условия затрудняют понимание кода и делают его менее гибким. Чтобы избежать этого, рекомендуется разбивать условия на отдельные, более понятные функции.
До рефакторинга:
def process_order(order):
if order.status == 'paid' and not order.shipped and orde
r.items_count > 0:
print("Processing order")
Сложное условие затрудняет понимание кода и может стать еще запутаннее с увеличением количества проверок.
После рефакторинга:
def is_order_ready_for_processing(order):
return order.status == 'paid' and not order.shipped and o
rder.items_count > 0
def process_order(order):
Теперь функция и переменные имеют понятные имена, и любой разработчик сможет легко понять, что функция выводит числа, большие 10.
Пример 3: Извлечение констант и конфигураций
Жестко закодированные значения затрудняют модификацию программы и могут привести к дублированию логики или появлению ошибок. Правильное решение — использование констант или настроек, которые легко можно изменить в одном месте.
До рефакторинга:
def calculate_shipping_cost(weight):
if weight > 5:
return 20
else:
return 10
После рефакторинга:
MAX_WEIGHT = 5
HIGH_WEIGHT_COST = 20
LOW_WEIGHT_COST = 10
def calculate_shipping_cost(weight):
if weight > MAX_WEIGHT:
return HIGH_WEIGHT_COST
else:
return LOW_WEIGHT_COST
Использование констант улучшает читабельность кода, делает его более наглядным и упрощает изменения в будущем, особенно если значение используется в нескольких местах.
Пример 4: Разделение больших функций на маленькие
Большие функции сложно читать, тестировать и модифицировать. Разделение функций на маленькие, которые выполняют одну задачу, облегчает их понимание и повторное использование.
До рефакторинга:
def handle_order(order):
if order.items_count == 0:
print("Order is empty")
else:
if order.status == 'paid':
if not order.shipped:
print("Processing order")
# Shipping logic here
else:
print("Order already shipped")
else:
print("Order not paid")
Эта функция содержит несколько уровней вложенности и логики, что делает её трудной для понимания.
После рефакторинга:
def is_order_empty(order):
return order.items_count == 0
def is_order_paid(order):
return order.status == 'paid'
def is_order_shipped(order):
return order.shipped
def handle_order(order):
if is_order_empty(order):
print("Order is empty")
elif is_order_paid(order):
if not is_order_shipped(order):
print("Processing order")
# Shipping logic here
else:
print("Order already shipped")
else:
print("Order not paid")
Мы вынесли отдельные логические проверки в маленькие функции, что упростило понимание и облегчило тестирование.
Пример 5: Использование «раннего выхода» из функций
Если функция выполняет несколько проверок перед выполнением основной логики, рекомендуется использовать "ранний выход" (early return). Это помогает избежать лишней вложенности и делает код более плоским и легко читаемым.
До рефакторинга:
def process_payment(order):
if order.status == 'paid':
if not order.shipped:
print("Order is ready for shipment")
else:
print("Order already shipped")
else:
print("Order not paid")
После рефакторинга:
def process_payment(order):
if order.status != 'paid':
print("Order not paid")
return
if order.shipped:
print("Order already shipped")
return
print("Order is ready for shipment")
Использование раннего выхода устраняет лишнюю вложенность и делает код легче для восприятия.
Пример 6: Разделение логики обработки ошибок
Разделение основной логики и обработки ошибок помогает сделать код чище и улучшает обработку исключений.
До рефакторинга:
def load_data(file_path):
try:
file = open(file_path)
data = file.read()
file.close()
return data
except FileNotFoundError:
print("File not found")
except IOError:
print("Error reading file")
После рефакторинга:
def read_file(file):
return file.read()
def load_data(file_path):
try:
with open(file_path) as file:
return read_file(file)
except FileNotFoundError:
print("File not found")
except IOError:
print("Error reading file")
Мы вынесли отдельную логику чтения файла в отдельную функцию, тем самым упростив основную функцию load_data и улучшив читаемость кода.
Использование дополнительных приемов рефакторинга, таких как улучшение именования, упрощение условий, извлечение констант, разделение больших функций и ранний выход из функций, помогает сделать код более понятным, гибким и легким в поддержке. Эти подходы позволяют улучшить структуру кода, что особенно важно для долгосрочных проектов и командной работы.
Заключение
Рефакторинг — важная часть процесса разработки, которая позволяет улучшать структуру кода, делая его более понятным, простым в поддержке и расширении. Регулярно практикуйте рефакторинг, следуя принципам DRY, KISS и SRP, и ваш код станет более устойчивым и удобным для работы.
Полезные материалы
Если вам интересно программирование и вы хотите улучшить свои знания и навыки в этой сфере, рекомендуем вам курсы от Гайк Инанц по следующим темам:
Вот что вы получите на каждом из курсов:
- Детальный разбор теории и объяснение сложные тем на простых аналогиях и понятных примерах, чтобы материал легко усваивался.
- Большое количество практических задач. Максимум времени в курсах уделено практике, чтобы вы могли закрепить знания и применять их в реальных ситуациях.
Присоединяйтесь: https://stepik.org/users/342567963/teach
Спасибо за внимание и до скорых встреч!