Рефакторинг — это когда ты меняешь код, но не меняешь его поведение. Звучит просто. На практике — это всегда боль. Особенно если проекту три года, авторы половины модулей уволились, а бизнес требует новые фичи вчера.
Хорошая новость: паттерны разработки и рефакторинга придумали именно для таких ситуаций. Это не академическая теория, а набор рецептов: что делать, когда ты видишь определённый тип проблем в коде.
Откуда вообще взялись эти паттерны
Крис Ричардсон в своей книге «Микросервисы: паттерны разработки и рефакторинга» систематизировал то, что большинство из нас делает интуитивно. Или не делает. Или делает плохо.
Книга Ричардсона — это не просто «микросервисы паттерны разработки и рефакторинга PDF для скачивания», как многие ищут. Это реально полезный материал, который объясняет, как переходить от монолита к микросервисам и какие паттерны применять на каждом этапе. Но даже если вы не планируете микросервисы — многие паттерны работают и в монолитах.
Как понять, что пора рефакторить
Есть классические code smells. Признаки того, что с кодом что-то не так.
Длинный метод. Если метод не помещается на экран — это проблема. Вы тратите больше времени на понимание кода, чем на его написание. На одном проекте у нас был метод на 800 строк. В нём была бизнес-логика, работа с базой, отправка email и парочка SQL-запросов, склеенных строками. Разобраться было невозможно.
Дублирование кода. Скопипастили один раз — окей. Два — терпимо. Три — пора выносить в общий метод. Честно? Большинство команд закрывает на это глаза до тех пор, пока не придёт требование «поменяйте эту логику везде». И тут выясняется, что логика скопирована в 17 местах.
Большой класс. Класс на 500+ строк делает слишком много. Это нарушает принцип единственной ответственности. Такие классы сложно тестировать и невозможно понимать.
Длинный список параметров. Если у метода больше 3-4 параметров — что-то не так. Скорее всего, часть из них можно объединить в объект.
Стрельба дробью. Когда одно изменение требует правок в десяти разных файлах. Это значит, что ответственность размазана по кодовой базе.
Методы рефакторинга: что делать с запахами
Паттерны рефакторинга — это конкретные техники. Вот основные.
Extract Method
Самый частый приём. Выделяем часть длинного метода в отдельный с понятным названием.
До:
def process_order(order):
# валидация
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Negative total")
# расчёт скидки
discount = 0
if order.customer.is_vip:
discount = order.total * 0.1
if order.total > 10000:
discount = max(discount, order.total * 0.05)
# сохранение
order.discount = discount
order.status = "processed"
db.save(order)
# отправка уведомления
send_email(order.customer.email, f"Order {order.id} processed")
log.info(f"Order {order.id} saved with discount {discount}")
После:
def process_order(order):
validate_order(order)
discount = calculate_discount(order)
apply_discount(order, discount)
save_and_notify(order)
def validate_order(order):
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Negative total")
def calculate_discount(order):
discount = 0
if order.customer.is_vip:
discount = order.total * 0.1
if order.total > 10000:
discount = max(discount, order.total * 0.05)
return discount
Теперь каждый метод делает одно дело. И названия методов работают как документация.
Replace Parameter with Object
Когда параметров много — объединяем их в объект.
До:
def create_user(name, email, age, country, city, street, zipcode):
# 7 параметров, легко перепутать порядок
pass
После:
@dataclass
class Address:
country: str
city: str
street: str
zipcode: str
def create_user(name: str, email: str, age: int, address: Address):
# 4 параметра, адрес как единое целое
pass
Decompose Conditional
Сложные условия выносим в отдельные методы с понятными названиями.
До:
if user.age >= 18 and user.country in ALLOWED_COUNTRIES and not user.is_banned and user.has_verified_email:
# логика
После:
if is_eligible_for_registration(user):
# логика
def is_eligible_for_registration(user):
return (user.age >= 18 and
user.country in ALLOWED_COUNTRIES and
not user.is_banned and
user.has_verified_email)
Паттерны микросервисов по Ричардсону
Крис Ричардсон выделил несколько ключевых паттернов именно для микросервисов. Они помогают при декомпозиции монолита и проектировании новых сервисов.
Saga. Распределённая транзакция через последовательность локальных транзакций. Каждая транзакция обновляет один сервис и публикует событие. По этому событию запускается следующая транзакция. Если что-то падает — выполняются компенсирующие транзакции.
API Gateway. Единая точка входа для клиентов. Маршрутизирует запросы к нужным сервисам, может делать агрегацию данных. Спасает от проблемы, когда фронтенду нужно звонить в 15 разных сервисов.
CQRS. Разделение моделей чтения и записи. Полезно когда операции чтения и записи имеют разную нагрузку и требования. На проекте с 1000 запросов на чтение и 10 на запись — это оправдано.
Event Sourcing. Хранение не текущего состояния, а последовательности событий, которые к нему привели. Позволяет восстановить состояние на любой момент времени. Но усложняет систему.
Как не сломать всё при рефакторинге
По моему опыту, главная проблема — не сам рефакторинг, а страх что-то сломать. И этот страх обоснован.
Покройте код тестами. Это база. Нет тестов — нет рефакторинга. По-хорошему, перед любым изменением нужно написать тесты на текущее поведение. Тогда вы сразу поймёте, если что-то сломали.
Делайте маленькие изменения. Не переписывайте весь модуль за раз. Один метод — один PR. Так проще найти ошибку, если она возникнет.
Используйте автоматические инструменты. Статические анализаторы, линтеры, AI code review. Они ловят то, что человек пропускает.
На одном проекте мы внедрили автоматический анализ каждого MR. AI-ассистент находил проблемы с производительностью, потенциальные NPE, нарушения стиля. Это сократило время code review примерно на 30%. И главное — стало меньше глупых багов в проде.
Практический пример: рефакторинг god class
Был у нас класс OrderManager на 2000 строк. Он делал всё: валидацию, расчёты, работу с базой, отправку уведомлений, генерацию отчётов.
Первый шаг — выделить зоны ответственности:
# Было: один класс на всё
class OrderManager:
def validate(self, order): ...
def calculate_total(self, order): ...
def save(self, order): ...
def send_notification(self, order): ...
def generate_report(self, order): ...
# Стало: несколько классов с одной ответственностью
class OrderValidator:
def validate(self, order): ...
class PriceCalculator:
def calculate_total(self, order): ...
class OrderRepository:
def save(self, order): ...
class NotificationService:
def send(self, order): ...
class ReportGenerator:
def generate(self, order): ...
Потом каждый класс можно тестировать изолированно. И менять, не ломая остальное.
Когда рефакторинг не нужен
Иногда код лучше не трогать.
Если модуль стабилен, покрыт тестами, и бизнес не просит изменений — оставьте как есть. Рефакторинг ради «чистоты» без реальной потребности — это трата времени.
Если дедлайн завтра — тоже не время для рефакторинга. Сначала сдаём фичу, потом улучшаем. Но обязательно заведите техдолг в бэклог.
Если команда не понимает, зачем это нужно. Рефакторинг без консенсуса — это конфликты на code review и саботаж.
Паттерны разработки и рефакторинга — это не серебряная пуля. Но это набор проверенных решений для типичных проблем. Книга Криса Ричардсона «Микросервисы: паттерны разработки и рефакторинга» даёт хорошую базу, но главный навык — научиться видеть проблемы в своём коде и применять нужные техники осознанно.
Если хочется автоматизировать часть проверки кода — попробуйте Distiq. Это AI-бот для code review, который анализирует MR и находит проблемы до того, как они улетят в прод. Интегрируется с GitLab, GitHub и GitVerse за пару минут. Платит своим временем — меньше разгребать багов потом.
