Распространённые ошибки: God Object, Singleton-глобал, Premature Optimization, Leaky Abstractions. Рефакторинг к чистому коду.
Антипаттерн — это решение, которое кажется правильным, но приводит к плохим последствиям.
Проблема: Класс, который знает и делает слишком много.
# ❌ ПЛОХО: God Object
class Application:
def __init__(self):
self.db = Database()
self.cache = Cache()
self.logger = Logger()
self.config = Config()
self.auth = AuthService()
self.email = EmailService()
# ... ещё 20 зависимостей ...
def process_order(self, order_id: int):
# 200 строк кода с бизнес-логикой, SQL, отправкой email...
pass
def generate_report(self, start, end):
# 150 строк отчётности
pass
def send_notifications(self):
# 100 строк уведомлений
pass
# ... ещё 50 методов ...
# Проблемы:
# 1. Невозможно тестировать
# 2. Любое изменение ломает что-то другое
# 3. Непонятно, за что отвечает класс# ✅ ХОРОШО: Разделение ответственности
class OrderService:
def __init__(self, db, email_service):
self._db = db
self._email = email_service
def process_order(self, order_id: int):
# Только бизнес-логика заказа
order = self._db.get_order(order_id)
# ... логика ...
self._email.send_confirmation(order)
class ReportService:
def __init__(self, db, cache):
self._db = db
self._cache = cache
def generate_report(self, start, end):
# Только отчётность
pass
class NotificationService:
def __init__(self, db, email_service, sms_service):
self._db = db
self._email = email_service
self._sms = sms_service
def send_notifications(self):
# Только уведомления
passПроблема: Singleton используется как глобальное состояние, что затрудняет тестирование.
# ❌ ПЛОХО: Singleton-глобал
class Config(Singleton):
def __init__(self):
self.debug = False
self.database_url = "prod://db"
# Везде в коде:
def get_user(user_id: int):
config = Config() # Скрытая зависимость!
db = Database(config.database_url)
return db.get(user_id)
# В тестах нельзя подменить конфигурацию!# ✅ ХОРОШО: Dependency Injection
class Config:
def __init__(self, debug: bool = False, database_url: str = ""):
self.debug = debug
self.database_url = database_url
def get_user(user_id: int, config: Config):
# Явная зависимость
db = Database(config.database_url)
return db.get(user_id)
# В production:
config = Config(debug=False, database_url="prod://db")
user = get_user(1, config)
# В тестах:
test_config = Config(debug=True, database_url="test://db")
user = get_user(1, test_config)Проблема: Оптимизация до понимания реальных узких мест.
# ❌ ПЛОХО: Преждевременная оптимизация
class OptimizedCache:
"""Сложный кэш с LRU, lock-free алгоритмами..."""
# 500 строк сложного кода
# Используется для кэширования 10 запросов в день# ✅ ХОРОШО: Сначала простой код
from functools import lru_cache
@lru_cache(maxsize=128)
def get_user(user_id: int):
return db.get_user(user_id)
# Профилируем, находим реальные узкие места
# Оптимизируем только если нужноПравило: Make it work, make it right, make it fast.
Проблема: Абстракция раскрывает детали реализации.
# ❌ ПЛОХО: ORM с протекающей абстракцией
class UserQuery:
def __init__(self, session):
self.session = session
def get_active_users(self):
# Клиент должен знать SQL для оптимизации!
return self.session.execute("""
SELECT * FROM users
WHERE is_active = true
AND last_login > NOW() - INTERVAL '30 days'
""")
# Абстракция "протекает" — нужно знать SQL# ✅ ХОРОШО: Чистая абстракция
class UserRepository:
def get_active_users(self) -> List[User]:
"""Возвращает активных пользователей за 30 дней"""
# Детали реализации скрыты
return self._db.query(User).filter(
User.is_active == True,
User.last_login > datetime.now() - timedelta(days=30)
).all()Проблема: Отсутствие структуры, перемешанная логика.
# ❌ ПЛОХО: Спагетти-код
def process_data(data):
result = []
for item in data:
if item > 0:
if item % 2 == 0:
x = item * 2
if x > 10:
x = x - 5
if x > 20:
x = x + 10
result.append(x)
else:
result.append(x)
else:
result.append(item)
else:
if item < -10:
result.append(item * -1)
elif item < -5:
result.append(item + 10)
# ... ещё 5 уровней вложенности ...
return result# ✅ ХОРОШО: Рефакторинг
def process_data(data: List[int]) -> List[int]:
result = []
for item in data:
if item <= 0:
processed = handle_negative(item)
if processed is not None:
result.append(processed)
continue
if item % 2 == 0:
processed = handle_even_positive(item)
if processed is not None:
result.append(processed)
else:
result.append(item)
return result
def handle_negative(item: int) -> Optional[int]:
if item < -10:
return item * -1
elif item < -5:
return item + 10
return None
def handle_even_positive(item: int) -> Optional[int]:
x = item * 2
if x > 20:
return x + 10
elif x > 10:
return x - 5
return xПроблема: Числа без объяснения смысла.
# ❌ ПЛОХО: Магические числа
def calculate_price(price: float) -> float:
if price > 1000:
return price * 0.9
elif price > 500:
return price * 0.95
return price
def is_valid_timeout(timeout: int) -> bool:
return 0 < timeout < 86400# ✅ ХОРОШО: Именованные константы
BULK_DISCOUNT_THRESHOLD = 1000
BULK_DISCOUNT_RATE = 0.9
SMALL_DISCOUNT_THRESHOLD = 500
SMALL_DISCOUNT_RATE = 0.95
SECONDS_PER_DAY = 86400
def calculate_price(price: float) -> float:
if price > BULK_DISCOUNT_THRESHOLD:
return price * BULK_DISCOUNT_RATE
elif price > SMALL_DISCOUNT_THRESHOLD:
return price * SMALL_DISCOUNT_RATE
return price
def is_valid_timeout(timeout: int) -> bool:
return 0 < timeout < SECONDS_PER_DAYПроблема: Метод использует данные другого класса больше, чем своего.
# ❌ ПЛОХО: Feature Envy
class Report:
def __init__(self, data: list):
self.data = data
class ReportGenerator:
def generate_html(self, report: Report) -> str:
# Использует report.data больше, чем свои данные
html = "<html>"
for item in report.data:
html += f"<div>{item.name}: {item.value}</div>"
html += "</html>"
return html# ✅ ХОРОШО: Переместить метод
class Report:
def __init__(self, data: list):
self.data = data
def to_html(self) -> str:
html = "<html>"
for item in self.data:
html += f"<div>{item.name}: {item.value}</div>"
html += "</html>"
return htmlПроблема: Метод длиннее 20-30 строк.
# ❌ ПЛОХО: Длинный метод
def process_order(order):
# Валидация (15 строк)
if not order.items:
raise ValueError("Empty order")
if not order.customer:
raise ValueError("No customer")
# ...
# Расчёт стоимости (20 строк)
subtotal = 0
for item in order.items:
# ...
# Применение скидок (15 строк)
# ...
# Сохранение (10 строк)
# ...
# Отправка уведомлений (15 строк)
# ...# ✅ ХОРОШО: Выделение методов
def process_order(order):
validate_order(order)
total = calculate_total(order)
total = apply_discounts(order, total)
save_order(order, total)
send_notifications(order)# ❌ ПЛОХО: Изменяемый аргумент по умолчанию
def add_item(item, items=[]): # Один список на все вызовы!
items.append(item)
return items
add_item(1) # [1]
add_item(2) # [1, 2] — а не [2]!# ✅ ХОРОШО: None как значение по умолчанию
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items# ❌ ПЛОХО: Ловит всё, включая KeyboardInterrupt
try:
process()
except:
pass # Скрывает все ошибки!# ✅ ХОРОШО: Конкретные исключения
try:
process()
except ValueError as e:
log_error(e)
except ConnectionError as e:
retry()# ❌ ПЛОХО: LBYL (Look Before You Leap) — не Pythonic
if key in my_dict and isinstance(my_dict[key], int):
value = my_dict[key]
else:
value = 0# ✅ ХОРОШО: EAFP (Easier to Ask Forgiveness than Permission)
try:
value = my_dict[key]
if not isinstance(value, int):
raise TypeError()
except (KeyError, TypeError):
value = 0| Антипаттерн | Проблема | Решение |
|---|---|---|
| God Object | Класс делает слишком много | Разделение на классы с одной ответственностью |
| Singleton-глобал | Скрытые зависимости | Dependency Injection |
| Premature Optimization | Сложный код без нужды | Сначала простой код, профилирование, потом оптимизация |
| Leaky Abstraction | Абстракция раскрывает детали | Скрытие реализации |
| Magic Numbers | Числа без имени | Именованные константы |
| Long Method | Метод > 30 строк | Выделение подметодов |
| Mutable Default | Один объект на все вызовы | None + создание внутри |
Главный принцип: Распознавайте антипаттерны и рефакторьте к чистому коду.
Поздравляем! Вы изучили все 23 классических паттерна GoF, архитектурные паттерны и asyncio-паттерны с упором на Pythonic-реализации.
Что дальше:
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.