Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Методология рефакторинга
Рефакторинг — это улучшение внутренней структуры кода БЕЗ изменения его внешнего поведения. Неправильный рефакторинг может сломать функциональность, поэтому нужна строгая методология.
Золотое правило: Red-Green-Refactor (TDD цикл)
1. RED — напишешь падающий тест
2. GREEN — напишешь минимальный код для прохождения
3. REFACTOR — улучшаешь код, сохраняя тесты зелёными
Пример рефакторинга с тестами
ДО: Плохой код (God Function)
# api/services/user_service.py
from sqlalchemy.orm import Session
from app.models import User
import re
from datetime import datetime, timedelta
def process_user_registration(data: dict, session: Session):
"""Функция делает ВСЁ: валидация, регистрация, отправка письма"""
# Валидация email
if not data.get('email'):
raise ValueError('Email required')
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', data['email']):
raise ValueError('Invalid email')
# Проверка пароля
if len(data.get('password', '')) < 8:
raise ValueError('Password too short')
if not any(c.isupper() for c in data['password']):
raise ValueError('Password must contain uppercase')
# Проверка существования пользователя
existing = session.query(User).filter(User.email == data['email']).first()
if existing:
raise ValueError('User already exists')
# Создание пользователя
user = User(
email=data['email'],
password_hash=hash_password(data['password']),
first_name=data.get('first_name', ''),
created_at=datetime.utcnow(),
is_active=True,
verification_token=generate_token(),
verification_expires_at=datetime.utcnow() + timedelta(hours=24)
)
session.add(user)
session.commit()
# Отправка email
import smtplib
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login('noreply@app.com', 'password')
message = f'Verify your email: {user.verification_token}'
server.sendmail('noreply@app.com', user.email, message)
server.close()
return {'user_id': user.id, 'email': user.email}
Проблемы:
- Функция делает слишком много (SRP нарушен)
- Сложно тестировать (зависит от БД и email сервиса)
- Хардкод для email отправки
- Валидация смешана с бизнес-логикой
ШАГ 1: Написать тесты (RED)
# tests/unit/services/test_user_service.py
import pytest
from app.services.user_service import UserRegistrationService
from app.services.email_service import EmailService
from app.validators import EmailValidator, PasswordValidator
from unittest.mock import Mock, patch
class TestUserRegistrationService:
@pytest.fixture
def email_service_mock(self):
return Mock(spec=EmailService)
@pytest.fixture
def user_repository_mock(self):
return Mock()
@pytest.fixture
def service(self, email_service_mock, user_repository_mock):
return UserRegistrationService(
user_repository=user_repository_mock,
email_service=email_service_mock
)
def test_successful_registration(self, service, user_repository_mock, email_service_mock):
"""Тест успешной регистрации"""
data = {
'email': 'john@example.com',
'password': 'SecurePassword123',
'first_name': 'John'
}
user_repository_mock.get_by_email.return_value = None
user_repository_mock.create.return_value = Mock(id=1, email='john@example.com')
result = service.register(data)
assert result['user_id'] == 1
assert result['email'] == 'john@example.com'
user_repository_mock.create.assert_called_once()
email_service_mock.send_verification.assert_called_once()
def test_email_already_exists(self, service, user_repository_mock):
"""Тест: пользователь уже зарегистрирован"""
data = {
'email': 'existing@example.com',
'password': 'SecurePassword123'
}
user_repository_mock.get_by_email.return_value = Mock(id=1)
with pytest.raises(ValueError, match='User already exists'):
service.register(data)
def test_invalid_email(self, service):
"""Тест: невалидный email"""
data = {
'email': 'invalid-email',
'password': 'SecurePassword123'
}
with pytest.raises(ValueError, match='Invalid email'):
service.register(data)
def test_weak_password(self, service):
"""Тест: слабый пароль"""
data = {
'email': 'john@example.com',
'password': 'weak123' # Нет заглавных букв
}
with pytest.raises(ValueError, match='Password must contain uppercase'):
service.register(data)
ШАГ 2: Написать минимальный код (GREEN)
# app/validators.py — Validator Pattern
from dataclasses import dataclass
import re
from typing import Optional
@dataclass
class ValidationError:
field: str
message: str
class EmailValidator:
@staticmethod
def validate(email: str) -> Optional[ValidationError]:
if not email:
return ValidationError('email', 'Email required')
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
return ValidationError('email', 'Invalid email')
return None
class PasswordValidator:
MIN_LENGTH = 8
@staticmethod
def validate(password: str) -> Optional[ValidationError]:
if len(password) < PasswordValidator.MIN_LENGTH:
return ValidationError('password', f'Password must be at least {PasswordValidator.MIN_LENGTH} characters')
if not any(c.isupper() for c in password):
return ValidationError('password', 'Password must contain uppercase')
if not any(c.isdigit() for c in password):
return ValidationError('password', 'Password must contain digit')
return None
# app/services/email_service.py — Email Service
from abc import ABC, abstractmethod
class EmailService(ABC):
@abstractmethod
def send_verification(self, email: str, token: str) -> bool:
pass
class SmtpEmailService(EmailService):
def __init__(self, smtp_host: str, smtp_port: int, sender_email: str, sender_password: str):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
self.sender_email = sender_email
self.sender_password = sender_password
def send_verification(self, email: str, token: str) -> bool:
import smtplib
try:
server = smtplib.SMTP(self.smtp_host, self.smtp_port)
server.starttls()
server.login(self.sender_email, self.sender_password)
message = f'Verify your email: {token}'
server.sendmail(self.sender_email, email, message)
server.close()
return True
except Exception as e:
print(f'Failed to send email: {e}')
return False
# app/repositories/user_repository.py
from sqlalchemy.orm import Session
from app.models import User
from typing import Optional
class UserRepository:
def __init__(self, session: Session):
self.session = session
def get_by_email(self, email: str) -> Optional[User]:
return self.session.query(User).filter(User.email == email).first()
def create(self, email: str, password_hash: str, first_name: str, verification_token: str, verification_expires_at):
from datetime import datetime
user = User(
email=email,
password_hash=password_hash,
first_name=first_name,
verification_token=verification_token,
verification_expires_at=verification_expires_at,
is_active=True,
created_at=datetime.utcnow()
)
self.session.add(user)
self.session.commit()
self.session.refresh(user)
return user
# app/services/user_service.py — User Registration Service
from app.repositories.user_repository import UserRepository
from app.services.email_service import EmailService
from app.validators import EmailValidator, PasswordValidator
from app.security import hash_password, generate_token
from datetime import datetime, timedelta
class UserRegistrationService:
def __init__(self, user_repository: UserRepository, email_service: EmailService):
self.user_repository = user_repository
self.email_service = email_service
self.email_validator = EmailValidator()
self.password_validator = PasswordValidator()
def register(self, data: dict) -> dict:
# Валидация
email_error = self.email_validator.validate(data.get('email', ''))
if email_error:
raise ValueError(email_error.message)
password_error = self.password_validator.validate(data.get('password', ''))
if password_error:
raise ValueError(password_error.message)
# Проверка существования
if self.user_repository.get_by_email(data['email']):
raise ValueError('User already exists')
# Создание пользователя
password_hash = hash_password(data['password'])
token = generate_token()
expires_at = datetime.utcnow() + timedelta(hours=24)
user = self.user_repository.create(
email=data['email'],
password_hash=password_hash,
first_name=data.get('first_name', ''),
verification_token=token,
verification_expires_at=expires_at
)
# Отправка email
self.email_service.send_verification(user.email, token)
return {'user_id': user.id, 'email': user.email}
ШАГ 3: Рефакторинг (REFACTOR)
Уже сделано в Step 2! Код разбит на слои:
- Validators — валидация
- Services — бизнес-логика
- Repositories — доступ к данным
- Email Service — отправка писем
Приёмы рефакторинга
1. Extract Method
# ДО
def calculate_discount(user_age, membership_years):
if user_age > 65:
base_discount = 0.15
elif membership_years > 5:
base_discount = 0.1
else:
base_discount = 0.05
seasonal_bonus = 0.02 if is_holiday() else 0
total = base_discount + seasonal_bonus
return total
# ПОСЛЕ
def calculate_discount(user_age, membership_years):
base_discount = _get_base_discount(user_age, membership_years)
seasonal_bonus = _get_seasonal_bonus()
return base_discount + seasonal_bonus
def _get_base_discount(user_age, membership_years):
if user_age > 65:
return 0.15
elif membership_years > 5:
return 0.1
return 0.05
def _get_seasonal_bonus():
return 0.02 if is_holiday() else 0
2. Replace Magic Numbers with Constants
# ДО
if user_age > 65:
discount = 0.15
if days_to_expiry < 3:
send_warning()
# ПОСЛЕ
SENIOR_AGE_THRESHOLD = 65
SENIOR_DISCOUNT = 0.15
EXPIRY_WARNING_DAYS = 3
if user_age > SENIOR_AGE_THRESHOLD:
discount = SENIOR_DISCOUNT
if days_to_expiry < EXPIRY_WARNING_DAYS:
send_warning()
3. Simplify Conditional
# ДО
if status == 'active' and role in ['admin', 'moderator']:
can_approve = True
else:
can_approve = False
# ПОСЛЕ
can_approve = status == 'active' and role in ['admin', 'moderator']
4. Replace Temp with Query
# ДО
temp = session.query(Order).filter(Order.status == 'completed').all()
for order in temp:
total += order.amount
# ПОСЛЕ
total = sum(
order.amount
for order in session.query(Order).filter(Order.status == 'completed')
)
Процесс безопасного рефакторинга
1. ✅ Убедись, что есть тесты
2. ✅ Запусти все тесты (GREEN)
3. ✅ Сделай небольшое изменение
4. ✅ Запусти тесты (должны быть GREEN)
5. ❌ Если тесты сломаны — откати изменение
6. ✅ Повторяй шаги 3-5
7. ✅ Commit после каждого успешного рефакторинга
Git workflow при рефакторинге
# Перед началом
git checkout -b refactor/user-service
# Небольшое изменение
git add .
git commit -m "Extract EmailValidator class"
# Запусти тесты
make test
# Если успешно — следующее изменение
git add .
git commit -m "Extract PasswordValidator class"
make test
# После всего рефакторинга
git push origin refactor/user-service
Признаки, что нужен рефакторинг
- Дублирование кода (DRY нарушен) — extract method
- Длинные функции (> 20 строк) — разбей на несколько
- Слишком много параметров (> 3) — используй объект
- Вложенные циклы — extract method
- Magic numbers и строки — constants
- Комплексные условия — extract method
- Побочные эффекты — изолируй
- Смешанные ответственности — SRP
Частые ошибки при рефакторинге
# ❌ Ошибка 1: Менять поведение и рефакторить одновременно
def refactor_and_add_feature():
# Это НЕ рефакторинг!
pass
# ✅ Правильно: сначала рефакторинг, потом фича
# Commit 1: Рефакторинг
# Commit 2: Новая фича
# ❌ Ошибка 2: Рефакторить без тестов
# Ты не поймешь, что сломал!
# ✅ Правильно: тесты → рефакторинг → тесты должны пройти
# ❌ Ошибка 3: Большие рефакторинги
# Сложно review, много потенциальных ошибок
# ✅ Правильно: маленькие, логические шаги
Лучшие практики
- Всегда пиши тесты перед рефакторингом
- Делай маленькие изменения (одна фича per commit)
- Запускай тесты после каждого изменения
- Используй IDE для автоматизации (rename, extract)
- Используй git bisect для поиска проблем
- Не рефакторь и не добавляй фичи одновременно
- Code review для важного рефакторинга
Рефакторинг — это не улучшение работы программы, это улучшение структуры кода. Всегда сохраняй функциональность!