SOLID, паттерны, слои архитектуры, зависимости, модульность
Архитектура — это решения, которые трудно изменить потом. Code review — ваш шанс поймать ошибки до того, как они станут наследием.
Принцип: Класс должен иметь одну причину для изменения.
# ❌ Нарушение SRP: класс делает слишком много
class UserService:
def get_user(self, user_id):
# SQL запрос
...
def send_welcome_email(self, user):
# Отправка email
...
def log_action(self, action):
# Логирование
...
# ✅ Соблюдение SRP: разделение ответственности
class UserRepository:
def get_user(self, user_id):
# Только работа с БД
...
class EmailService:
def send_welcome_email(self, user):
# Только отправка email
...
class Logger:
def log_action(self, action):
# Только логирование
...Что проверять:
Принцип: Классы должны быть открыты для расширения, но закрыты для изменения.
# ❌ Нарушение OCP: нужно модифицировать код для нового типа
class DiscountCalculator:
def calculate(self, user_type, amount):
if user_type == "regular":
return amount * 0.9
elif user_type == "vip":
return amount * 0.8
# Для нового типа нужно изменить этот метод!
elif user_type == "premium":
return amount * 0.85
# ✅ Соблюдение OCP: расширение через наследование
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, amount: float) -> float:
pass
class RegularDiscount(DiscountStrategy):
def calculate(self, amount: float) -> float:
return amount * 0.9
class VIPDiscount(DiscountStrategy):
def calculate(self, amount: float) -> float:
return amount * 0.8
# Новый тип — новый класс, без изменения существующих
class PremiumDiscount(DiscountStrategy):
def calculate(self, amount: float) -> float:
return amount * 0.85
class DiscountCalculator:
def __init__(self, strategy: DiscountStrategy):
self.strategy = strategy
def calculate(self, amount: float) -> float:
return self.strategy.calculate(amount)Принцип: Зависите от абстракций, а не от конкретных классов.
# ❌ Нарушение DIP: зависимость от конкретной реализации
class MySQLDatabase:
def query(self, sql):
...
class UserService:
def __init__(self):
self.db = MySQLDatabase() # Жёсткая зависимость!
# ✅ Соблюдение DIP: зависимость от абстракции
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def query(self, sql: str):
pass
class UserService:
def __init__(self, db: Database): # Внедрение зависимости
self.db = dbЧто проверять:
new ConcreteClass() внутри бизнес-логики┌─────────────────────────────────┐
│ Presentation Layer │ ← Controllers, Views, API
│ (Controllers, Handlers) │
├─────────────────────────────────┤
│ Business Logic Layer │ ← Services, Domain Models
│ (Services, Use Cases) │
├─────────────────────────────────┤
│ Data Access Layer │ ← Repositories, DAO
│ (Repositories, DAO) │
├─────────────────────────────────┤
│ Database / External API │
└─────────────────────────────────┘
# ❌ Нарушение: контроллер делает запрос к БД напрямую
class UserController:
def get_user(self, user_id):
result = db.execute("SELECT * FROM users WHERE id = ?", user_id)
return jsonify(result)
# ✅ Правильно: контроллер → сервис → репозиторий
class UserController:
def __init__(self, user_service: UserService):
self.user_service = user_service
def get_user(self, user_id):
user = self.user_service.get_user(user_id)
return jsonify(user.to_dict())
class UserService:
def __init__(self, user_repo: UserRepository):
self.user_repo = user_repo
def get_user(self, user_id):
return self.user_repo.find_by_id(user_id)Что проверять:
✅ Правильно:
modules/
├── core/ # Никого не импортирует
├── domain/ # Импортирует core
├── application/ # Импортирует domain, core
└── infrastructure/# Импортирует всё (адаптеры)
❌ Неправильно: циклические зависимости
module_a.py → import module_b
module_b.py → import module_a
# ❌ Циклическая зависимость
# user.py
from order import Order # Импорт Order
class User:
def get_orders(self) -> list[Order]:
...
# order.py
from user import User # Импорт User!
class Order:
def __init__(self, user: User):
...
# ✅ Решение: через строковые аннотации или общий модуль
# user.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from order import Order
class User:
def get_orders(self) -> list[Order]:
...
# order.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from user import User
class Order:
def __init__(self, user: User):
...Принцип: Одна сущность может иметь разные модели в разных контекстах.
# ❌ Плохо: одна модель для всего
class User:
id: int
name: str
email: str
password_hash: str
billing_address: str
shipping_address: str
role: str
permissions: list
# 50 полей для всех случаев жизни
# ✅ Хорошо: разные модели для разных контекстов
# Auth Context
class AuthUser:
id: int
email: str
password_hash: str
role: str
# Billing Context
class BillingCustomer:
id: int
name: str
billing_address: str
payment_method: str
# Shipping Context
class ShippingRecipient:
id: int
name: str
shipping_address: str
phone: strЧто проверять:
Ключевая мысль: Архитектурные ошибки дорого исправлять. Проверяйте SOLID, слои, зависимости и границы контекстов в каждом крупном PR.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.