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

Хотелось бы заниматься рефакторингом проекта

1.0 Junior🔥 101 комментариев
#DevOps и инфраструктура#Django

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

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

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

Рефакторинг проекта: видение и подход

Да, я заинтересован в рефакторинге

Да, я активно практикую и люблю рефакторинг. Считаю, что переоценить его значение невозможно. Хороший рефакторинг — это инвестиция в скорость разработки и качество кода.

Почему рефакторинг критичен

1. Снижение технического долга

Технический долг растёт с каждой строкой кода. Без рефакторинга проект становится всё медленнее:

# ДО рефакторинга — спагетти код
def process_order(order_id, user_id, db):
    order = db.query("SELECT * FROM orders WHERE id = ?", order_id)
    if not order:
        raise Exception("Order not found")
    
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    if not user:
        raise Exception("User not found")
    
    if user['balance'] < order['total']:
        db.update("UPDATE orders SET status = ? WHERE id = ?", "payment_failed", order_id)
        raise Exception("Insufficient funds")
    
    db.update("UPDATE orders SET status = ? WHERE id = ?", "processing", order_id)
    db.update("UPDATE users SET balance = ? WHERE id = ?", user['balance'] - order['total'], user_id)
    
    for item in db.query("SELECT * FROM order_items WHERE order_id = ?", order_id):
        db.update("UPDATE products SET stock = stock - ? WHERE id = ?", item['quantity'], item['product_id'])
    
    return {"status": "ok", "order_id": order_id}

ПОСЛЕ рефакторинга:

class OrderProcessingError(Exception):
    """Base order processing error."""
    pass

class InsufficientFundsError(OrderProcessingError):
    pass

class OrderNotFoundError(OrderProcessingError):
    pass

class OrderService:
    def __init__(self, order_repo: OrderRepository, user_repo: UserRepository):
        self.order_repo = order_repo
        self.user_repo = user_repo
    
    async def process_order(self, order_id: str, user_id: str) -> OrderDTO:
        """Process order with clear validation and error handling."""
        order = await self.order_repo.get(order_id)
        if not order:
            raise OrderNotFoundError(f"Order {order_id} not found")
        
        user = await self.user_repo.get(user_id)
        if not user:
            raise UserNotFoundError(f"User {user_id} not found")
        
        if user.balance < order.total:
            await self.order_repo.update_status(order_id, OrderStatus.PAYMENT_FAILED)
            raise InsufficientFundsError(
                f"User balance {user.balance} < order total {order.total}"
            )
        
        await self._deduct_funds(user_id, order.total)
        await self._reserve_stock(order_id)
        await self.order_repo.update_status(order_id, OrderStatus.PROCESSING)
        
        return OrderDTO.from_entity(order)
    
    async def _deduct_funds(self, user_id: str, amount: float) -> None:
        """Deduct funds from user balance."""
        await self.user_repo.deduct_balance(user_id, amount)
    
    async def _reserve_stock(self, order_id: str) -> None:
        """Reserve stock for all order items."""
        items = await self.order_repo.get_items(order_id)
        for item in items:
            await self.product_repo.reduce_stock(item.product_id, item.quantity)

Виды рефакторинга

1. Рефакторинг на уровне функций

Разбиение больших функций на маленькие с одной ответственностью:

# ❌ Плохо: функция делает 5 вещей
def import_users_from_csv(filename):
    with open(filename) as f:
        for row in f:
            parts = row.strip().split(',')
            user = User(email=parts[0], name=parts[1])
            db.add(user)
            try:
                user.send_welcome_email()
            except:
                pass
            db.commit()

# ✅ Хорошо: каждая функция имеет одну задачу
def import_users_from_csv(filename: str) -> list[User]:
    """Import users from CSV file."""
    users = parse_csv_file(filename)
    return [create_user(u) for u in users]

async def parse_csv_file(filename: str) -> list[dict]:
    """Parse CSV file and return dicts."""
    with open(filename) as f:
        return [parse_row(row) for row in f]

def parse_row(row: str) -> dict:
    """Parse single CSV row."""
    parts = row.strip().split(',')
    return {"email": parts[0], "name": parts[1]}

async def create_user(data: dict) -> User:
    """Create user and send welcome email."""
    user = await User.create(**data)
    await send_welcome_email(user.email)
    return user

2. Рефакторинг на уровне структуры данных

Замена примитивных типов на доменные объекты:

# ❌ Плохо: используем словари везде
def validate_user(user_dict: dict) -> bool:
    if not user_dict.get("email"):
        return False
    if "@" not in user_dict["email"]:
        return False
    if len(user_dict.get("password", "")) < 8:
        return False
    return True

# ✅ Хорошо: используем типы и value objects
from pydantic import BaseModel, EmailStr, Field

class Email(BaseModel):
    value: EmailStr
    
    def is_valid(self) -> bool:
        return "@" in self.value

class Password(BaseModel):
    value: str = Field(min_length=8)
    
    def is_valid(self) -> bool:
        return len(self.value) >= 8

class User(BaseModel):
    email: Email
    password: Password
    
    def is_valid(self) -> bool:
        return self.email.is_valid() and self.password.is_valid()

3. Рефакторинг на уровне архитектуры

Переход от монолита к слоистой архитектуре:

# ДО: всё в одном файле
# app.py (1000+ строк)
from fastapi import FastAPI

app = FastAPI()

@app.post("/users")
def create_user(email: str, password: str, db):
    # validation, database access, email sending — всё вместе
    pass

# ПОСЛЕ: Clean Architecture + DDD
project/
├── domain/
│   ├── entities.py          # User, Email, Password
│   ├── services.py          # UserService (business logic)
│   └── exceptions.py        # DomainException
├── application/
│   ├── use_cases.py         # CreateUserUseCase
│   └── dto.py               # CreateUserRequest, CreateUserResponse
├── infrastructure/
│   ├── repositories.py      # UserRepository (DB access)
│   └── email_client.py      # EmailClient (external service)
└── presentation/
    └── api.py               # FastAPI routes (thin handlers)

Как я провожу рефакторинг

Правило 1: TDD (Test-Driven Refactoring)

Основное правило: сначала пишу тесты, потом рефакторю:

# 1. Пишу тесты для существующего кода
def test_order_processing():
    service = OrderService(mock_order_repo, mock_user_repo)
    result = await service.process_order("order_123", "user_456")
    assert result.status == OrderStatus.PROCESSING

def test_insufficient_funds():
    service = OrderService(mock_order_repo, mock_user_repo)
    with pytest.raises(InsufficientFundsError):
        await service.process_order("order_123", "poor_user")

# 2. Рефакторю код, тесты остаются зелёными
# 3. Добавляю новые тесты для улучшений

Правило 2: Boy Scout Rule

Каждый раз оставляю код чище, чем он был:

# Исходный код выглядит так:
def get_user_orders(user_id):
    u = db.query("SELECT id, nm, em FROM users WHERE id = ?", user_id)
    ords = db.query("SELECT id, amt, st FROM orders WHERE uid = ?", user_id)
    return {"u": u, "ords": ords}

# Boy Scout: улучшу при прохождении
def get_user_with_orders(user_id: str) -> UserWithOrders:
    """Get user and their orders."""
    user = self.user_repo.get(user_id)
    orders = self.order_repo.find_by_user(user_id)
    return UserWithOrders(user=user, orders=orders)

Правило 3: Red-Green-Refactor Cycle

Для каждого изменения:

  1. RED — падающий тест
  2. GREEN — минимальный код для прохождения
  3. REFACTOR — улучшение без изменения поведения

Метрики рефакторинга

# Отслеживаю улучшения
metrics = {
    "cyclomatic_complexity": 4,    # было 12, теперь 4
    "avg_function_length": 15,     # было 45, теперь 15
    "test_coverage": 0.92,         # было 0.65, теперь 0.92
    "code_duplication": 0.08,      # было 0.25, теперь 0.08
    "dependencies_per_module": 3,  # было 12, теперь 3
}

Когда НЕ рефакторить

НЕ рефакторь:

  • Когда нет тестов (сначала пиши тесты!)
  • Когда дедлайн завтра (отложи на позже)
  • Когда код работает и не мешает (YAGNI)
  • Когда ты не понимаешь, что именно улучшаешь

Рефакторь когда:

  • Разработка замедляется
  • Регулярно находятся баги
  • Новые фичи сложно добавлять
  • Code review становится болезненным

Инструменты

# Проверка сложности кода
radon cc backend -a

# Дублирование
vulture backend

# Покрытие
pytest --cov=backend --cov-report=html

# Type checking
mypy backend

# Linting
ruff check backend

Итог

Рефакторинг — это не лишняя работа, а обязательная часть разработки. Код, который не переделывается, всегда становится хуже. Я люблю рефакторинг, потому что он:

  1. Снижает технический долг
  2. Упрощает добавление новых фич
  3. Уменьшает число багов
  4. Делает код более понятным для команды
  5. Позволяет выявлять ошибки проектирования