Хотелось бы заниматься рефакторингом проекта
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Рефакторинг проекта: видение и подход
Да, я заинтересован в рефакторинге
Да, я активно практикую и люблю рефакторинг. Считаю, что переоценить его значение невозможно. Хороший рефакторинг — это инвестиция в скорость разработки и качество кода.
Почему рефакторинг критичен
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
Для каждого изменения:
- RED — падающий тест
- GREEN — минимальный код для прохождения
- 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
Итог
Рефакторинг — это не лишняя работа, а обязательная часть разработки. Код, который не переделывается, всегда становится хуже. Я люблю рефакторинг, потому что он:
- Снижает технический долг
- Упрощает добавление новых фич
- Уменьшает число багов
- Делает код более понятным для команды
- Позволяет выявлять ошибки проектирования