Запахи кода и антипаттерны
Код редко ломается внезапно — сначала он начинает «пахнуть».
Запах кода (code smell) — поверхностный признак того, что в коде, скорее всего, есть более глубокая проблема дизайна. Сам по себе он не баг, но повод присмотреться.
Запахи — это словарь, на котором команда обсуждает качество кода: вместо размытого «как-то некрасиво» можно сказать «здесь God object» или «тут feature envy», и все понимают, о чём речь, и какой рефакторинг напрашивается. Антипаттерн — это запах, доросший до устойчивого способа делать плохо: повторяемое решение, которое кажется удобным, но систематически вредит.
Зачем это на практике
Распознавание запахов — навык дешёвой профилактики. Заметить раздувающийся класс на 200 строк проще, чем разбирать монстра на 2000: чем раньше пойман запах, тем дешевле лечение. Этот навык окупается и на код-ревью — отзыв «вот тут feature envy, перенеси метод» конкретнее и полезнее, чем «мне не нравится». Ниже — самые частые запахи, как их увидеть глазами и чем лечить. Хорошая новость: лечение почти всегда сводится к небольшому, повторяемому набору приёмов рефакторинга — «выделить метод», «выделить класс», «переместить метод», «заменить число константой». Их стоит довести до автоматизма; современные IDE умеют выполнять многие из них одной командой, не ломая поведение.
God object — божественный объект
Один класс знает и делает слишком много: держит половину состояния системы и управляет всем.
Признаки: класс с именем вроде Manager, Helper, Utils, сотни строк, десятки несвязанных методов, его импортируют отовсюду. Это прямое нарушение SRP. «Плохо»:
class App:
def parse(self, raw): ...
def validate(self, data): ...
def save(self, data): ...
def send_email(self, user): ...
def render_html(self, data): ... # всё в одном классе
Лечение — «выделить класс»: разносим обязанности по отдельным классам (Parser, Validator, Repository, Mailer), а App лишь оркестрирует.
Длинный метод
Функция, которая делает слишком много шагов и не помещается на экран.
Длинный метод тяжело читать (надо держать в голове весь контекст) и тяжело тестировать (много веток). Лечение — «выделить метод»: каждый осмысленный шаг становится отдельной функцией с говорящим именем. «Хорошо»:
def normalize(name):
return name.strip().title()
def is_valid(name):
return len(name) >= 2
def register(name):
name = normalize(name)
if not is_valid(name):
return "отклонено"
return f"добро пожаловать, {name}"
print(register(" анна "))
print(register("x"))
Вывод:
добро пожаловать, Анна отклонено
Хорошее имя извлечённой функции работает как комментарий, который не устаревает: тело register читается почти как текст.
Feature envy — зависть к чужим данным
Метод одного класса больше интересуется данными другого класса, чем своими.
Если метод то и дело дёргает геттеры чужого объекта и считает что-то на их основе, логика, скорее всего, должна жить внутри этого объекта. «Плохо»: внешняя функция лезет в поля заказа.
class Order:
def __init__(self, price, qty):
self.price = price
self.qty = qty
def total(order): # завидует данным Order
return order.price * order.qty
Лечение — «переместить метод»: переносим расчёт туда, где живут данные.
class Order:
def __init__(self, price, qty):
self.price = price
self.qty = qty
def total(self):
return self.price * self.qty
print(Order(100, 3).total())
Вывод:
300
Спагетти-код
Поток управления так запутан, что проследить его невозможно: всё связано со всем.
Глубокая вложенность if, флаги, прыжки логики, отсутствие границ модулей. Спагетти обычно возникает не сразу, а из накопленных «быстрых правок». Лечится сочетанием приёмов: ранние return вместо лестницы else, выделение методов, введение чётких слоёв. Часто именно здесь уместно вспомнить про паттерны: Strategy убирает ветвление по типу, State приводит в порядок хаос переходов между состояниями.
Другие частые запахи
| Запах | Признак | Чем лечить |
| Дублирование | один код в нескольких местах | выделить метод/класс (DRY) |
| Длинный список параметров | функция на 6+ аргументов | объединить в объект-параметр |
| Магические числа | x * 0.18 без названия | именованная константа |
| Комментарии-костыли | комментарий объясняет странный код | переписать код понятно |
Как это работает под капотом
Почему запахи вообще накапливаются? Из-за энтропии изменений: каждая правка под давлением сроков чуть ухудшает структуру, если не вкладываться обратно. Это явление называют техническим долгом — как с кредитом, маленькие «проценты» (лишняя минута на чтение мутного метода) набегают на каждой будущей правке. Рефакторинг — это выплата долга. Запахи — индикаторы того, где «процент» уже высок и пора платить.
Частые ошибки
- Гнаться за всеми запахами разом. Запах — не баг. Рефакторить стоит то, что вы и так собираетесь менять, а не «всё подряд ради чистоты».
- Рефакторинг без тестов. Менять структуру вслепую — значит ловить регрессии. Сначала тесты на текущее поведение, потом правка.
- Большой рефакторинг «за один присест». Безопаснее серия мелких шагов, каждый из которых оставляет код рабочим.
- Путать запах с осознанным выбором. Иногда длинный, но линейный метод читается лучше, чем десяток мелких. Контекст важнее каталога.
Итоги
- Запах кода — поверхностный сигнал о возможной проблеме дизайна, а не сам баг.
- God object и длинный метод лечатся выделением класса и метода (опора на SRP).
- Feature envy лечится перемещением логики туда, где живут данные.
- Спагетти-код приводят в порядок ранними return, слоями и иногда паттернами (Strategy, State).
- Запахи — это карта технического долга; гасить его лучше там, где всё равно работаешь, и под прикрытием тестов.