AI-система рефакторинга кода
Рефакторинг без AI упирается в стоимость: senior-разработчик понимает, что нужно сделать, но 80% работы — механическое переписывание. AI берёт эту механику на себя, оставляя человеку архитектурные решения и ревью результата.
Виды рефакторинга и подходы
Структурный рефакторинг (extract method, move class, rename) — хорошо поддаётся автоматизации, высокая точность AI.
Паттерновый рефакторинг (переход с callbacks на async/await, добавление dependency injection) — требует понимания контекста, AI справляется при правильном промпте.
Архитектурный рефакторинг (monolith → microservices, God Object → SRP) — AI генерирует план и черновик, финальные решения за человеком.
Система рефакторинга
from anthropic import Anthropic
from pathlib import Path
import subprocess
import ast
from typing import Literal
client = Anthropic()
REFACTORING_TYPES = {
"extract_function": "Выдели повторяющийся код в отдельные функции",
"simplify_conditions": "Упрости сложные условные выражения",
"add_type_hints": "Добавь аннотации типов везде где их нет",
"async_migration": "Преобразуй синхронный код в async/await",
"error_handling": "Добавь явную обработку ошибок",
"dataclass_conversion": "Преобразуй dict-based структуры в dataclasses/Pydantic",
"dependency_injection": "Добавь dependency injection вместо глобальных объектов",
"remove_duplication": "Устрани дублирование кода (DRY)",
}
class CodeRefactorer:
def refactor(
self,
source_code: str,
refactoring_type: str,
context: str = "",
preserve_interface: bool = True,
) -> dict:
"""Выполняет рефакторинг и возвращает diff"""
instructions = REFACTORING_TYPES.get(refactoring_type, refactoring_type)
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=8096,
system=f"""Ты — senior разработчик, выполняющий рефакторинг Python кода.
Задача рефакторинга: {instructions}
Правила:
- {"Сохраняй публичный интерфейс (имена функций, аргументы, возвращаемые типы)" if preserve_interface else "Интерфейс можно менять"}
- Не меняй логику — только структуру кода
- Если добавляешь новые вспомогательные функции — размещай их выше основных
- Сохраняй все существующие комментарии и docstrings
- Добавляй комментарий # REFACTORED: <краткое описание> где были ключевые изменения""",
messages=[{
"role": "user",
"content": f"""Выполни рефакторинг:
```python
{source_code}
{f"Контекст проекта:{chr(10)}{context}" if context else ""}
Верни:
- Рефакторированный код
- Список изменений (bullet points)
- Потенциальные риски если есть
Формат ответа:
<рефакторированный код>
Изменения:
- ...
Риски:
-
...""" }] )
text = response.content[0].text # Парсим ответ refactored_code = "" if "```python" in text: refactored_code = text.split("```python")[1].split("```")[0].strip() changes = [] risks = [] if "**Изменения:**" in text: changes_section = text.split("**Изменения:**")[1].split("**Риски:**")[0] changes = [line.strip("- ").strip() for line in changes_section.splitlines() if line.strip().startswith("-")] if "**Риски:**" in text: risks_section = text.split("**Риски:**")[1] risks = [line.strip("- ").strip() for line in risks_section.splitlines() if line.strip().startswith("-")] return { "original": source_code, "refactored": refactored_code, "changes": changes, "risks": risks, }
### Async migration — пример реального рефакторинга
```python
def migrate_to_async(source_file: str) -> str:
"""Мигрирует синхронный код на async/await"""
source = Path(source_file).read_text()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=8096,
system="""Мигрируй Python код с синхронного на async/await.
Правила:
- requests → httpx.AsyncClient
- time.sleep(n) → asyncio.sleep(n)
- threading.Thread → asyncio.create_task
- queue.Queue → asyncio.Queue
- Добавь async/await к функциям, которые делают I/O
- Оставь синхронными функции без I/O (чистые вычисления)
- Замени for loop на asyncio.gather где функции независимы""",
messages=[{
"role": "user",
"content": f"Мигрируй на async/await:\n\n```python\n{source}\n```\n\nВерни только код."
}]
)
text = response.content[0].text
if "```python" in text:
return text.split("```python")[1].split("```")[0].strip()
return text
def add_type_hints(source_file: str) -> str:
"""Добавляет аннотации типов к функциям"""
source = Path(source_file).read_text()
# Находим функции без аннотаций
tree = ast.parse(source)
unannotated = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
has_annotations = (
any(arg.annotation for arg in node.args.args) or
node.returns is not None
)
if not has_annotations:
unannotated.append(node.name)
if not unannotated:
return source
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=8096,
messages=[{
"role": "user",
"content": f"""Добавь аннотации типов Python (PEP 484) к функциям: {', '.join(unannotated)}
Правила:
- Используй from __future__ import annotations для forward references
- Для Optional используй X | None (Python 3.10+)
- Для коллекций: list[str], dict[str, int], tuple[int, ...]
- Для неизвестных типов используй Any из typing
```python
{source}
Верни полный файл с добавленными аннотациями.""" }] )
text = response.content[0].text
if "```python" in text:
return text.split("```python")[1].split("```")[0].strip()
return text
### Рефакторинг с тестовой страховкой
```python
def safe_refactor(
source_file: str,
refactoring_type: str,
test_file: str = None,
) -> dict:
"""Выполняет рефакторинг только если тесты проходят"""
source = Path(source_file).read_text()
refactorer = CodeRefactorer()
# Сначала запускаем существующие тесты
if test_file and Path(test_file).exists():
result = subprocess.run(
["python", "-m", "pytest", test_file, "-v", "--tb=short"],
capture_output=True, text=True
)
if result.returncode != 0:
return {"success": False, "error": "Tests failing before refactoring", "test_output": result.stdout}
# Выполняем рефакторинг
refactoring = refactorer.refactor(source, refactoring_type)
# Создаём backup
backup_file = source_file + ".bak"
Path(backup_file).write_text(source)
# Записываем рефакторированный код
Path(source_file).write_text(refactoring["refactored"])
# Запускаем тесты снова
if test_file and Path(test_file).exists():
result = subprocess.run(
["python", "-m", "pytest", test_file, "-v", "--tb=short"],
capture_output=True, text=True
)
if result.returncode != 0:
# Откатываемся
Path(source_file).write_text(source)
return {
"success": False,
"error": "Tests failing after refactoring — rolled back",
"changes": refactoring["changes"],
"test_output": result.stdout,
}
return {
"success": True,
"changes": refactoring["changes"],
"risks": refactoring["risks"],
"backup": backup_file,
}
Практический кейс: Django-монолит
Ситуация: Django-проект, 6 лет разработки, 45000 строк. 3 проблемы: нет type hints (новые разработчики тратят много времени на понимание типов), много дублирования в view-функциях, 12 God Object классов.
Применённые рефакторинги (за 3 недели):
-
add_type_hintsдля всехviews.pyфайлов — автоматически, 2 часа -
extract_functionдля view-функций > 50 строк — автоматически + ревью, 1 неделя -
remove_duplicationв сервисном слое — автоматически, 3 дня
Результаты:
- Type annotations coverage: 12% → 87%
- Средняя длина функции: 68 строк → 23 строки
- Дублирование кода (SonarQube metric): -43%
- Время онбординга нового разработчика: 3 недели → 1.5 недели
Один God Object (OrderService, 1800 строк) потребовал ручного архитектурного решения — AI предложил 3 варианта декомпозиции, разработчики выбрали оптимальный.
Сроки
- Базовый рефакторинг одного типа для файла: 1–2 дня
- Система safe_refactor с тестовой страховкой: 3–5 дней
- Batch-рефакторинг всей кодовой базы: 2–3 недели
- Интеграция в IDE как команда: 1 неделя







