← Назад к вопросам

Что лучше делать сразу рефакторинг архитектуры или производительности?

2.0 Middle🔥 121 комментариев
#DevOps и инфраструктура#FastAPI и Flask#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Рефакторинг архитектуры vs Оптимизация производительности

Прямой ответ

Архитектура важнее производительности. Но это не значит, что производительность можно игнорировать. Вот структурированный подход.

Принцип: YAGNI + Премature Optimization

YAGNI (You Aren't Gonna Need It):

# ❌ Неправильно — оптимизируем всё на вот
array = []
for i in range(1_000_000):
    # Сложная оптимизация памяти
    array.append(optimize_memory(i))

# ✅ Правильно — сначала рабочий код
array = [i for i in range(1_000_000)]

# Потом, если профилировка покажет узкое место:
array = np.array([i for i in range(1_000_000)])  # Если нужно

Premature Optimization Evil: Дональд Кнут: "The root of all evil is premature optimization."

# ❌ Неправильно — оптимизируем без данных
class Cache:
    def __init__(self):
        self.data = {}  # Кэширование, которого ненужно

service = Cache()
result = service.data.get("key")  # Слабо используется

# ✅ Правильно — сначала работает, потом профилируем
class Service:
    def get_data(self, key):
        return fetch_from_db(key)

# Через месяц: "Хм, это медленно"
# Профилируем: видим, что fetch_from_db вызывается 1000 раз за секунду
# ТОГДА добавляем кэширование

Иерархия приоритетов

priorities = {
    1: "Код работает (correctness)",
    2: "Архитектура понятна и поддерживается (maintainability)",
    3: "Код покрыт тестами (reliability)",
    4: "Производительность адекватна для пользователей (performance)",
    5: "Микрооптимизации (micro-optimization)"
}

# Обрабатываем в таком порядке!

Когда рефакторить архитектуру?

Сигналы:

signs_to_refactor_architecture = [
    "Сложно добавлять новые фичи",
    "Изменение в одном месте ломает другие части",
    "Тесты сложно писать",
    "Новые разработчики теряются в коде",
    "Код дублируется",
    "Нарушены SOLID принципы",
    "Слабая связанность модулей",
    "Сложно отменить или заменить компоненты"
]

# Даже ОДНОГО знака достаточно для рефакторинга

Пример: монолитный класс

# ❌ Плохая архитектура
class UserManager:
    def create_user(self, email, password):
        # Валидация
        if "@" not in email:
            raise ValueError()
        
        # Хеширование пароля
        hashed = bcrypt.hash(password)
        
        # Сохранение в БД
        db.execute(f"INSERT INTO users VALUES ('{email}', '{hashed}')")
        
        # Отправка email
        smtp.send_email(email, "Welcome!")
        
        # Логирование
        logger.info(f"User {email} created")
        
        # Analytics
        analytics.track("user_created", {"email": email})

# Все ответственности в одном классе!
# Сложно тестировать, сложно модифицировать

Правильная архитектура:

# ✅ Разделение ответственности (Single Responsibility)

class EmailValidator:
    def validate(self, email: str) -> bool:
        return "@" in email

class PasswordHasher:
    def hash(self, password: str) -> str:
        return bcrypt.hash(password)

class UserRepository:
    def save(self, email: str, hashed_password: str) -> User:
        db.execute(f"INSERT INTO users VALUES (...)")
        return User(email=email)

class EmailService:
    def send_welcome(self, email: str) -> None:
        smtp.send_email(email, "Welcome!")

class AnalyticsService:
    def track_user_creation(self, email: str) -> None:
        analytics.track("user_created", {"email": email})

class Logger:
    def log_user_created(self, email: str) -> None:
        logger.info(f"User {email} created")

# Оркестратор (Use Case)
class CreateUserUseCase:
    def __init__(self, validator, hasher, repo, email_svc, analytics, logger):
        self.validator = validator
        self.hasher = hasher
        self.repo = repo
        self.email_svc = email_svc
        self.analytics = analytics
        self.logger = logger
    
    def execute(self, email: str, password: str) -> User:
        if not self.validator.validate(email):
            raise ValueError("Invalid email")
        
        hashed = self.hasher.hash(password)
        user = self.repo.save(email, hashed)
        
        self.email_svc.send_welcome(email)
        self.analytics.track_user_creation(email)
        self.logger.log_user_created(email)
        
        return user

# Тестирование теперь простое
def test_create_user():
    mock_repo = MockUserRepository()
    use_case = CreateUserUseCase(
        EmailValidator(),
        PasswordHasher(),
        mock_repo,
        MockEmailService(),
        MockAnalyticsService(),
        MockLogger()
    )
    
    user = use_case.execute("alice@example.com", "secret123")
    assert user.email == "alice@example.com"

# Легко заменять компоненты!

Когда оптимизировать производительность?

Сигналы:

signs_to_optimize_performance = [
    "Пользователи жалуются на медлительность",
    "Профилировка показала узкое место",
    "Инфраструктура дорога из-за нагрузки",
    "База данных перегружена",
    "API делает 1000 запросов вместо 1",
    "Загрузка страницы > 3 секунд"
]

# Главное: профилируй, не гадай!

Пример оптимизации:

# ❌ Медленный код (N+1 проблема)
users = db.query(User).all()  # SELECT * FROM users
for user in users:
    posts = db.query(Post).filter(Post.user_id == user.id).all()  # SELECT * FROM posts WHERE user_id = ?
    # Если 1000 пользователей, это 1001 запрос!

# ✅ Оптимизировано (JOIN)
users = db.query(User).join(Post).all()  # SELECT * FROM users JOIN posts
# Один запрос вместо 1001!

# ✅ Кэширование результата
from functools import lru_cache

@lru_cache(maxsize=128)
def get_user_posts(user_id: int):
    return db.query(Post).filter(Post.user_id == user_id).all()

# Первый вызов: из БД
posts1 = get_user_posts(1)
# Второй вызов с тем же user_id: из кэша (мгновенно)
posts2 = get_user_posts(1)

Правило: Профилируй перед оптимизацией

import cProfile
import pstats

def slow_function():
    result = []
    for i in range(1_000_000):
        result.append(i * 2)
    return result

# Профилируем
cProfile.run('slow_function()', 'stats')
stats = pstats.Stats('stats')
stats.print_stats()  # Видим, где время тратится

# После профилирования видим:
# - 80% времени в цикле
# - 20% в append
# Оптимизируем узкое место:

def fast_function():
    return [i * 2 for i in range(1_000_000)]  # List comprehension быстрее

Стратегия: incremental refactoring

# Месяц 1: Фиксим архитектурные проблемы
# - Разбиваем монолит на модули
# - Вводим DI (Dependency Injection)
# - Пишем тесты

# Месяц 2: Код работает хорошо
# - Добавляем логирование
# - Мониторим performance

# Месяц 3: Видим узкие места в мониторинге
# - Профилируем
# - Оптимизируем конкретные места

# Месяц 4: Production работает быстро
# - Радуемся жизни

Реальный пример из моего опыта

Сценарий:

# Стартап, слабая архитектура, всё работает
# Код в одном файле, функции 500+ строк

# Квартал 1: Пытаемся добавлять фичи
# - Каждое изменение ломает что-то
# - Новые разработчики теряются
# - Рефакторинг архитектуры КРИТИЧЕН

# Квартал 2: Архитектура улучшена
# - Модули разделены
# - Тесты добавлены
# - Легче добавлять фичи

# Квартал 3: Пользователей стало 100x
# - Сервер падает
# - Профилируем: N+1 в SQL, неправильный кэш
# - Оптимизируем производительность

# Квартал 4: Система масштабируется
# - Архитектура позволяет добавлять сервисы
# - Оптимизация позволяет справляться с нагрузкой

Практическое правило выбора

if system_is_hard_to_change_or_test:
    # Рефакторим архитектуру
    refactor_architecture()
elif users_are_complaining_about_speed:
    # Профилируем и оптимизируем
    profile_and_optimize()
else:
    # Просто добавляем фичи
    add_features()

Антипаттерны

# ❌ Неправильно: оптимизируем без данных
"У нас будет миллион пользователей!" (на 100 пользователях)
# → Добавляем кэширование, которое никогда не используется

# ❌ Неправильно: игнорируем плохую архитектуру
"Это работает, не трогай!" (но никто не может добавить фичу за неделю)
# → Техдолг растёт, velocity падает

# ❌ Неправильно: микрооптимизации на Python
`result = ' '.join(str(x) for x in arr)`  # vs
`result = "".join(map(str, arr))`  # Разница 5% в одной операции
# → Тратим время на микроны, а архитектура горит

Мой совет

good_development_process = {
    "Week 1": "Code is correct, tests pass",
    "Week 2": "Code is maintainable, architecture clean",
    "Week 3": "Code is used by users, features work",
    "Week 4": "Profile if needed, optimize bottlenecks"
}

# Порядок имеет значение!

Заключение

Архитектура важнее производительности, потому что:

  1. Плохая архитектура замораживает разработку — невозможно добавлять фичи
  2. Хорошая архитектура позволяет легко оптимизировать — просто меняем компоненты
  3. Производительность можно добавить позже — профилируем и оптимизируем
  4. Архитектуру сложнее менять потом — легче с самого начала

Правильная стратегия:

  1. Сначала: Код работает + архитектура понятна
  2. Потом: Добавляем тесты + документацию
  3. Когда нужно: Профилируем и оптимизируем узкие места

Помни: "Make it work, make it right, make it fast." В этом порядке.