
Вайб-кодинг (vibe coding) — описать подробно
Вайб-кодинг — это подход к программированию, где человек описывает задачу на обычном языке, а ИИ генерирует код, помогает его править и доводить до рабочего состояния. Идея в том, что разработчик больше формулирует цель и проверяет результат, чем пишет всё вручную [1] [2].
Как это работает
Обычно процесс выглядит так:
— Вы формулируете, что нужно сделать: например, «сделай форму входа с email, паролем и кнопкой».
— ИИ предлагает код или сразу несколько вариантов реализации.
— Вы уточняете требования, исправляете поведение, просите рефакторинг.
— Вы тестируете результат и при необходимости откатываете изменения через контроль версий [1] [3].
Главная особенность вайб-кодинга — работа идет итерациями, через диалог с моделью, а не через ручное пошаговое написание всего кода [4] [3].
Чем он полезен
Вайб-кодинг особенно хорош для быстрого прототипирования, MVP, лендингов, простых веб-приложений и шаблонных задач. Он ускоряет старт проекта и помогает быстро проверить идею без долгой ручной разработки [1] [5].
Еще он полезен новичкам: можно собирать простые приложения, даже не зная синтаксис языка в деталях, если умеешь четко описывать задачу и проверять результат [2] [6].
Ограничения и риски
У этого подхода есть важные минусы. ИИ может сгенерировать код, который выглядит рабочим, но содержит ошибки, плохую архитектуру или уязвимости, поэтому результат нужно внимательно проверять [3] [7].
Для сложных систем, где важны производительность, безопасность и надежность, традиционная разработка все еще остается необходимой. Вайб-кодинг лучше всего работает как ускоритель, а не как полная замена инженерной дисциплины [3] [8].
Где уместен
Лучше всего вайб-кодинг подходит для:
— Быстрых прототипов.
— Простых внутренних утилит.
— Лендингов и небольших веб-сервисов.
— Экспериментов с UI и бизнес-логикой.
— Рутинных фрагментов кода и шаблонов [5] [8].
Хуже он подходит для:
— Крупных backend-систем.
— Критичных финансовых или медицинских сервисов.
— Нагрузочных и высоконадежных систем.
— Проектов, где нужен строгий контроль качества и безопасности [3] [7].
Как делать лучше
Чтобы вайб-кодинг не превратился в хаос, обычно помогают несколько правил: давать конкретные инструкции, решать одну задачу за раз, просить модель задавать уточняющие вопросы и обязательно сохранять версии кода для отката [1] [9].
Практически это означает, что лучше писать не «сделай мне приложение», а «сделай страницу логина на React с двумя полями, валидацией email и обработкой ошибки входа». Чем точнее запрос, тем качественнее результат [3] [6].
Простая формула
Если совсем коротко, вайб-кодинг — это «сначала идея и описание, потом генерация и правка кода с помощью ИИ» [2] [4].
Это особенно удобно, когда нужно быстро получить рабочую версию, но не стоит слепо доверять сгенерированному коду без проверки [1] [7].
Могу дальше показать:
— как вайб-кодить на практике;
— какие промпты работают лучше;
— чем вайб-кодинг отличается от классической разработки.
—
⁂
Как программировать в стиле М Фаулера с использованием вайб-кодинга — описать подробно, дать ссылки на литературу
Да — если соединить вайб-кодинг с подходом Мартина Фаулера, получится не «печатать код по наитию», а управлять изменениями маленькими безопасными шагами, где ИИ помогает быстро реализовать следующий шаг, а человек удерживает дизайн, тесты и эволюцию архитектуры [11] [12].
Какой здесь принцип
У Фаулера ключевая идея такая: хороший код — это код, который легко читать, менять и сопровождать; поэтому улучшение дизайна должно идти через небольшие поведенчески сохранные изменения, то есть через refactoring [11] [13].
В TDD-подходе, который он поддерживает, цикл выглядит как Red-Green-Refactor: сначала тест, затем минимальная реализация, затем улучшение структуры кода [12].
Если перенести это в вайб-кодинг, ИИ становится ускорителем цикла, а не заменой инженерного мышления [14] [12].
Рабочая модель
Практически это выглядит так:
— Сформулировать маленькую бизнес-задачу.
— Попросить ИИ написать минимальный код и тесты.
— Запустить тесты и посмотреть, что именно сломано.
— Попросить ИИ сделать один небольшой рефакторинг без изменения поведения.
— Повторять, пока код не станет чище, но все тесты продолжают проходить [12] [11].
Такой стиль особенно хорошо сочетается с идеей Фаулера о «small steps»: большие изменения разбиваются на цепочку маленьких, проверяемых преобразований [15] [11].
Вайб-кодинг здесь полезен тем, что ускоряет написание чернового кода, тестов, адаптеров и промежуточных рефакторингов [14] [13].
Как именно кодить
Лучше всего мыслить не файлами, а микро-изменениями.
Например: «выдели метод», «убери дублирование», «замени флаг-аргумент на полиморфизм», «вынеси доменную логику из контроллера», «добавь тест на граничный случай» [13] [16].
Полезный стиль промпта для ИИ:
— «Сначала предложи минимальное изменение, не меняя поведение».
— «Сначала напиши тест, который фиксирует текущее поведение».
— «Теперь рефакторни только этот метод».
— «Не трогай соседние модули».
— «Покажи, какие инварианты ты сохраняешь» [12] [11].
Это очень близко к фаулеровскому мышлению: сначала сделать код безопасным для изменения, затем улучшать его понемногу [17] [18].
Что брать у Фаулера
Если переводить идеи Фаулера в вайб-кодинг, то ядро такое:
— Refactoring как повседневная практика, а не отдельная «уборка» после проекта [11] [13].
— Тесты как страховка для изменений [12] [19].
— Маленькие шаги вместо больших переписываний [15] [11].
— Работа с legacy через seams, то есть через места, где поведение можно менять без прямого вмешательства в сердце системы [17] [18].
— Поиск code smells как сигналов, что пора менять структуру, а не только багфиксить симптомы [16] [13].
Как использовать ИИ
ИИ в этом стиле лучше использовать как помощника по четырем ролям:
— Генератор черновика.
— Партнер по рефакторингу.
— Генератор тестов.
— Аналитик code smells и точек декомпозиции [14] [16].
Плохой сценарий — попросить ИИ «сделай всё приложение».
Хороший сценарий — поручить один шаг: «разбей этот класс», «добавь тесты», «вынеси зависимость», «замени ветвление на стратегию», «покажи, как сделать seam для legacy-кода» [17] [13].
Практический шаблон
Для проекта можно держать такой цикл:
— Определить поведение.
— Добавить или обновить тест.
— Сгенерировать минимальный код.
— Проверить.
— Рефакторить маленькими шагами.
— Повторить [12] [11].
Пример: если нужно вынести логику расчета комиссии, сначала пишешь тесты на текущие кейсы, потом просишь ИИ выделить отдельную функцию или объект, затем постепенно убираешь дублирование и упрощаешь интерфейс [16] [13].
Именно так вайб-кодинг становится не хаотичным «кодогенератором», а инструментом аккуратной эволюции системы [11] [17].
Литература
Вот хорошие источники, с которых стоит начать:
— Martin Fowler, Refactoring: Improving the Design of Existing Code [11] [20].
— Martin Fowler, Catalog of Refactorings [13].
— Martin Fowler, Test-Driven Development [12].
— Martin Fowler, The Practical Test Pyramid [19] [21].
— Michael Feathers, Working Effectively with Legacy Code [18].
— Martin Fowler, Legacy Seam [17].
— Martin Fowler, Refactoring to Patterns [15].
Где читать
Официальные страницы и материалы:
— https://martinfowler.com/books/refactoring.html [11]
— https://refactoring.com/catalog/ [13]
— https://martinfowler.com/bliki/TestDrivenDevelopment.html [12]
— https://martinfowler.com/articles/practical-test-pyramid.html [19]
— https://martinfowler.com/bliki/LegacySeam.html [17]
— http://objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf [18]
Если хочешь, я могу следующим сообщением собрать для тебя готовый пошаговый workflow «vibe coding + Fowler» для реального проекта на Python/TypeScript.
⁂
Примеры промптов для ИИ в стиле Фаулера
Ниже — набор практичных промптов в стиле Фаулера: то есть не «сгенерируй всё целиком», а «сделай маленький безопасный шаг, сохрани поведение, улучши дизайн, опирайся на тесты» [40] [41].
Базовый шаблон
Используй такой каркас почти для любой задачи:
— Контекст: что это за система и где боль.
— Цель: что нужно улучшить.
— Ограничения: не менять поведение, не трогать соседние модули, сохранить API.
— Критерий успеха: тесты проходят, код стал проще, дублирование уменьшилось.
— Формат ответа: план, изменения, риски, тесты [40] [42].
Пример промпта:
У меня есть фрагмент legacy-кода. Найди самый безопасный следующий маленький шаг для улучшения дизайна, не меняя поведение. Сначала предложи тесты, затем минимальный рефакторинг, затем укажи, какие запахи кода ты устранил. Не переписывай всё целиком [43] [40].
Для нового кода
Если проект только начинается, Фаулерский стиль означает сразу закладывать простоту и тестируемость.
Помоги спроектировать модуль так, чтобы его было легко рефакторить потом. Предложи минимальную доменную модель, границы ответственности, тесты и точки расширения. Избегай преждевременной абстракции [40] [44].
Сгенерируй минимальную реализацию функции с TDD-подходом: сначала тест, потом код, потом один рефакторинг. Не добавляй лишних слоёв и паттернов без явной необходимости [41] [40].
Для рефакторинга
Это самый «фаулеровский» сценарий.
У меня есть метод с несколькими ответственностями. Разбей его на более маленькие части, сохранив поведение. Сделай изменения маленькими шагами и покажи, какой тест защищает каждый шаг [40] [42].
Найди code smells в этом фрагменте: длинный метод, дублирование, флаг-аргументы, смешение доменной и инфраструктурной логики. Для каждого запаха предложи один небольшой рефакторинг и объясни, почему он безопасен [45] [42].
Предложи план refactoring-by-steps: шаг 1, шаг 2, шаг 3. Каждый шаг должен быть обратимым и проверяемым тестом [46] [40].
Для legacy-кода
Фаулерский стиль особенно хорошо работает с legacy, если просить ИИ искать seams и точки изоляции.
У меня legacy-модуль без хороших тестов. Найди seam, через который можно безопасно добавить тестирование, не переписывая систему. Предложи самый дешёвый способ зафиксировать текущее поведение [43] [47].
Помоги обернуть внешний сервис адаптером, чтобы можно было протестировать бизнес-логику отдельно. Сначала опиши точку изоляции, потом предложи изменения в коде [43] [44].
Для этого старого модуля предложи стратегию постепенной модернизации: что тестировать сначала, что вынести в отдельный слой, что не трогать до появления покрытия [47] [40].
Для работы с ИИ
Здесь важно управлять моделью как напарником по инженерной дисциплине.
Не пиши полный код сразу. Сначала задай мне 3 уточняющих вопроса, потом предложи план маленьких шагов, потом сгенерируй только первый шаг [48] [41].
Когда предлагаешь изменения, обязательно указывай: какой инвариант сохраняется, какой риск появляется, какой тест это проверит [41] [40].
Если видишь несколько вариантов дизайна, сравни их по поддерживаемости, тестируемости и стоимости изменения. Не выбирай самый «умный» вариант без необходимости [40] [42].
Для TDD
Это очень полезный стиль промптинга.
Напиши тесты, которые описывают поведение системы на уровне намерений, а не реализации. Затем предложи минимальную реализацию, достаточную для прохождения тестов [41] [44].
Сначала сформулируй failing test для граничного случая, затем покажи минимальный production-code fix, затем предложи один рефакторинг без изменения поведения [41] [40].
Проверь, есть ли в этом коде тестируемые границы. Если их нет, предложи, как ввести их с минимальными изменениями [44] [43].
Для архитектуры
Фаулер бы одобрил вопросы про границы, слои и простоту изменений.
Помоги разделить систему на слои так, чтобы доменная логика не зависела от UI и внешних API. Предложи минимальные интерфейсы и места для dependency injection [40] [44].
Предложи архитектуру, которая позволит менять одну подсистему без каскадных правок. Какие seams и абстракции нужны прямо сейчас, а какие преждевременны [43] [40].
Хорошие ограничения
Вот фразы, которые сильно улучшают результат:
— «Не меняй поведение».
— «Ограничься одним файлом или одним методом».
— «Не добавляй новые библиотеки».
— «Сначала тест, потом код».
— «Сделай минимальный шаг».
— «Покажи, как откатить изменение».
— «Не оптимизируй заранее».
— «Не переписывай целиком» [41] [40].
Плохие формулировки
Избегай таких запросов:
— «Сделай красиво».
— «Полностью перепиши архитектуру».
— «Добавь лучшие практики».
— «Сделай enterprise-grade» [40] [45].
Проблема в том, что они слишком расплывчаты и толкают модель к избыточному дизайну или большим изменениям без проверки [42] [40].
Мини-шаблон для копипаста
У меня есть фрагмент кода.
Цель: [что улучшить].
Ограничения: не менять поведение, не трогать соседние модули, не добавлять новые зависимости.
Сначала предложи тесты, которые зафиксируют текущее поведение.
Затем предложи только один маленький рефакторинг.
Объясни, какой code smell он убирает и почему изменение безопасно [41] [40] [42].
Литература
Для этой манеры мышления особенно полезны:
— Martin Fowler, Refactoring: Improving the Design of Existing Code [40].
— Martin Fowler, Catalog of Refactorings [42].
— Martin Fowler, Test-Driven Development [41].
— Martin Fowler, The Practical Test Pyramid [44].
— Michael Feathers, Working Effectively with Legacy Code [47].
— Martin Fowler, Legacy Seam [43].
Могу следующим сообщением дать 20 готовых промптов именно под Python, TypeScript или Java/Spring.
⁂
Как использовать эти промпты для рефакторинга legacy кода
Использовать эти промпты для рефакторинга legacy-кода лучше как последовательный конвейер, а не как один большой запрос: сначала понять систему, потом зафиксировать поведение тестами, затем делать маленькие безопасные изменения [58] [59] [60].
Рабочий порядок
— Сначала дай ИИ фрагмент legacy-кода и попроси провести аудит без изменений: найти роли модулей, зависимости, code smells, рискованные места и точки для seam [60] [59].
— Затем попроси написать или предложить тесты, которые фиксируют текущее поведение, включая странные edge-cases, чтобы рефакторинг не сломал неочевидную логику [61] [60].
— После этого используй промпты на один маленький шаг: выделение метода, удаление дублирования, инкапсуляция зависимости, упрощение ветвления, разделение ответственности [58] [62].
— В конце попроси модель показать, что изменилось, какие риски остались и как откатить правку через git [60] [63].
Как строить промпт
Хороший промпт для legacy-кода должен содержать четыре блока: контекст, цель, ограничения и формат ответа [60] [58].
Например: «Вот модуль заказа. Он работает, но плохо читается. Не меняй поведение. Сначала предложи тесты на текущее поведение, потом один безопасный рефакторинг, потом объясни, какой smell он убрал» [60] [64].
Чем жестче ограничение на поведение, тем меньше шанс, что ИИ начнет «улучшать» логику и ломать бизнес-кейс [60] [61].
Практический цикл
Удобно работать по циклу:
— Аудит.
— Тесты на текущее поведение.
— Один рефакторинг.
— Прогон тестов.
— Следующий маленький шаг [60] [58].
Такой цикл очень близок к фаулеровскому стилю: изменения делаются маленькими порциями, а безопасность обеспечивает тестовая сетка [58] [61].
Если модуль сильно связанный, сначала попроси ИИ найти seam — место, где можно отделить внешние зависимости от доменной логики [59] [63].
Примеры промптов
1. Аудит legacy без правок
Проанализируй этот код без изменения. Объясни, что он делает, где у него зависимости, какие есть code smells, какие места наиболее рискованные для рефакторинга и где можно поставить seam для тестов [60] [59].
2. Зафиксировать поведение
Сгенерируй набор тестов, которые фиксируют текущее поведение этого модуля, включая граничные случаи и странные edge-cases. Не исправляй код, только опиши и покажи тесты [61] [60].
3. Один шаг рефакторинга
Сделай только один безопасный рефакторинг этого метода. Поведение не должно измениться. Сначала покажи план шага, затем код, затем перечисли, какой smell устранен [58] [62].
4. Работа с зависимостями
Вынеси внешнюю зависимость за интерфейс или адаптер, чтобы доменная логика стала тестируемой отдельно. Не меняй публичное API [59] [63].
5. Стратегия миграции
Предложи пошаговый план модернизации этого legacy-модуля: что тестировать первым, что изолировать, что оставить без изменений до появления покрытия [60] [63].
Что просить у модели
Для legacy особенно полезно просить не только код, но и обоснование.
Пусть ИИ отвечает в формате: «что изменил, почему это безопасно, что осталось неизменным, какой тест это подтверждает» [61] [58].
Если модуль большой, проси работать по одному файлу или одному методу за раз, иначе модель почти неизбежно сделает слишком крупную правку [60] [62].
Полезная тактика
Для удобства можно задавать роли:
— «Ты — ревьюер legacy-кода.»
— «Ты — специалист по безопасному рефакторингу.»
— «Ты — помощник по TDD.»
— «Ты — эксперт по seams и адаптерам» [59] [61].
Это помогает модели держать дисциплину: не переписывать лишнее, не предлагать преждевременную архитектуру и не ломать работающий код [58] [60].
Литература и опора
Для этой работы лучше всего опираться на:
— Martin Fowler, Refactoring: Improving the Design of Existing Code [58].
— Martin Fowler, Catalog of Refactorings [62].
— Martin Fowler, Test-Driven Development [61].
— Martin Fowler, Legacy Seam [59].
— Michael Feathers, Working Effectively with Legacy Code [63].
Если хочешь, я могу следующим сообщением дать готовый промпт-шаблон для рефакторинга legacy-кода, который можно просто вставлять в ChatGPT/Claude/Cursor.
⁂
Примеры рефакторинга FastAPI legacy кода с этими промптами
Ниже — несколько типовых примеров рефакторинга legacy FastAPI-кода «в стиле Фаулера»: маленькими шагами, с фиксацией поведения тестами, с выделением seams и вынесением бизнес-логики из fat endpoints [74] [75] [76].
Идея для FastAPI
В legacy FastAPI часто встречаются три проблемы: толстые route handlers, смешение HTTP-слоя и бизнес-логики, а также прямое использование БД и внешних сервисов прямо в endpoint-функции [76] [77].
Фаулеровский подход здесь — не переписывать всё сразу, а сначала зафиксировать текущее поведение, затем выделить seam, потом вынести логику в service/repository-слои и сохранить публичное API [74] [75].
Пример 1: fat endpoint
До
@router.post (»/orders/ {order_id} /approve»)
async def approve_order (
order_id: int,
db: Session = Depends (get_db),
user: User = Depends (get_current_user),
):
order = db.query(Order).filter(Order.id == order_id).first ()
if not order:
raise HTTPException (status_code=404, detail=«Order not found»)
if order.status!= «pending»:
raise HTTPException (status_code=400, detail=«Invalid status»)
if user.role!= «manager»:
raise HTTPException (status_code=403, detail=«Forbidden»)
order.status = «approved»
order. approved_by = user.id
db.commit ()
db.refresh (order)
send_email(order.customer_email, «approved»)
audit_log («order_approved», order.id, user.id)
return {«id»: order.id, «status»: order.status}
В этом коде HTTP-логика, авторизация, бизнес-правила, работа с БД и побочные эффекты смешаны в одном месте, что затрудняет тестирование и изменение поведения [76] [78].
Промпт к ИИ
Проанализируй этот FastAPI endpoint. Не меняй поведение.
После первого шага
class OrderService:
def __init__ (self, db: Session):
self. db = db
def approve (self, order_id: int, user: User):
order = self.db.query(Order).filter(Order.id == order_id).first ()
if not order:
raise OrderNotFound ()
if order.status!= «pending»:
raise InvalidOrderStatus ()
if user.role!= «manager»:
raise PermissionDenied ()
order.status = «approved»
order. approved_by = user.id
self.db.commit ()
self.db.refresh (order)
return order
def get_order_service (db: Session = Depends (get_db)) -> OrderService:
return OrderService (db)
@router.post (»/orders/ {order_id} /approve»)
async def approve_order (
order_id: int,
user: User = Depends (get_current_user),
service: OrderService = Depends (get_order_service),
):
order = service. approve (order_id, user)
send_email(order.customer_email, «approved»)
audit_log («order_approved», order.id, user.id)
return {«id»: order.id, «status»: order.status}
Это уже лучше, потому что endpoint начинает работать как контроллер, а бизнес-решение перемещается в service-слой, который легче покрывать тестами отдельно от HTTP [76] [77].
По-Фаулеру это хороший первый шаг: небольшой, локальный и поведенчески безопасный [75] [79].
Пример 2: выделение seam
Проблема
Частая беда legacy FastAPI — прямые вызовы email, Kafka, Redis, внешних API или audit-функций внутри бизнес-метода. Такие зависимости мешают изолированному тестированию и делают код хрупким [74] [80].
Промпт к ИИ
Найди в этом FastAPI service внешние зависимости, мешающие тестированию.
Предложи seam для изоляции email и audit-логирования.
Сделай один маленький шаг: заверни побочные эффекты в интерфейс NotificationPort и AuditPort, не меняя поведения сервиса [74] [81].
После
class NotificationPort (Protocol):
def order_approved (self, email: str) -> None:…
class AuditPort (Protocol):
def order_approved (self, order_id: int, user_id: int) -> None:…
class OrderService:
def __init__ (
self,
db: Session,
notifications: NotificationPort,
audit: AuditPort,
):
self. db = db
self.notifications = notifications
self.audit = audit
def approve (self, order_id: int, user: User):
order = self.db.query(Order).filter(Order.id == order_id).first ()
if not order:
raise OrderNotFound ()
if order.status!= «pending»:
raise InvalidOrderStatus ()
if user.role!= «manager»:
raise PermissionDenied ()
order.status = «approved»
order. approved_by = user.id
self.db.commit ()
self.db.refresh (order)
self.notifications.order_approved(order.customer_email)
self.audit.order_approved(order.id, user.id)
return order
Теперь появился seam: в тестах можно подставить fake-реализации портов и проверить только бизнес-поведение без реальных внешних эффектов [74] [80].
Это очень фаулеровский ход: не спорить с legacy-кодом в лоб, а сначала открыть место для безопасной подмены зависимостей [74] [75].
Пример 3: слишком много Depends
В реальных FastAPI legacy-проектах endpoint может принимать слишком много зависимостей, а затем просто прокидывать их дальше. Это признак того, что композиция зависимостей находится не в том слое [82] [76].
До
@router.get (»/ {order_id}»)
async def get_order (
order_id: int,
session: Session = Depends (get_db),
actor_service: ActorService = Depends (get_actor_service),
event_service: EventService = Depends (get_event_service),
item_service: ItemService = Depends (get_item_service),
comment_service: CommentService = Depends (get_comment_service),
):
service = OrderService (
session=session,
actor_service=actor_service,
event_service=event_service,
item_service=item_service,
comment_service=comment_service,
)
return service.get_order (order_id)
Промпт к ИИ
Упрости FastAPI endpoint в legacy-модуле.
Не меняй поведение.
Вынеси сборку сложного OrderService в dependency provider, чтобы endpoint зависел только от одного сервиса [82] [76] [77].
После
def get_order_service (
session: Session = Depends (get_db),
actor_service: ActorService = Depends (get_actor_service),
event_service: EventService = Depends (get_event_service),
item_service: ItemService = Depends (get_item_service),
comment_service: CommentService = Depends (get_comment_service),
) -> OrderService:
return OrderService (
session=session,
actor_service=actor_service,
event_service=event_service,
item_service=item_service,
comment_service=comment_service,
)
@router.get (»/ {order_id}»)
async def get_order (
order_id: int,
service: OrderService = Depends (get_order_service),
):
return service.get_order (order_id)
Такой шаг уменьшает связность HTTP-слоя и делает endpoint чище без большого переписывания архитектуры [76] [77].
Это не «идеальная архитектура за раз», а маленькое улучшение формы кода, что хорошо соответствует каталогу рефакторингов Фаулера [79] [75].
Пример 4: постепенное покрытие тестами
Когда legacy FastAPI-код плохо тестируется, не надо сразу пытаться писать идеальные unit-тесты на всё. Сначала имеет смысл зафиксировать текущее поведение на уровне API или service-метода, а уже потом дробить систему дальше [81] [83].
Промпт к ИИ
Напиши characterization tests для этого FastAPI endpoint.
Цель — зафиксировать текущее поведение, включая странные ветки и edge-cases.
Не исправляй баги, только опиши и покрой текущее поведение тестами через TestClient или через сервисный слой, если это дешевле [81] [83].
Это полезно, потому что characterization tests снижают риск «рефакторинга вслепую», особенно если бизнес-логика уже не до конца понятна команде [81] [75].
Готовые промпты
Вот хорошие промпты именно для FastAPI legacy:
— «Проанализируй этот endpoint и выдели HTTP-логику, бизнес-логику, доступ к данным и побочные эффекты. Предложи один минимальный рефакторинг без изменения поведения» [75] [76].
— «Сгенерируй characterization tests для этого FastAPI route, чтобы зафиксировать текущее поведение перед рефакторингом» [81] [83].
— «Найди seam для изоляции внешнего API/почты/очереди, чтобы сервис можно было тестировать без реальных интеграций» [74] [80].
— «Вынеси бизнес-логику из fat endpoint в service-слой, сохрани сигнатуру маршрута и response shape» [76] [77].
— «Упрости dependency graph: endpoint должен зависеть от одного сервиса, а не от пяти отдельных зависимостей» [82] [76].
— «Предложи пошаговый план рефакторинга этого FastAPI-модуля в 3–5 маленьких шагов, каждый с тестом и возможностью отката» [84] [75].
Как применять на практике
Самый рабочий способ такой:
— Вставляешь один endpoint или один service-класс.
— Просишь ИИ сначала объяснить текущую структуру.
— Затем просишь тесты на текущее поведение.
— Потом — один рефакторинг за раз.
— После каждого шага запускаешь тесты и только потом переходишь дальше [75] [83].
Если хочешь, я могу следующим сообщением сделать полный разбор одного реального FastAPI legacy endpoint: до → промпты → после → тесты.
⁂
Пример рефакторинга FastAPI на чистую архитектуру в стиле lobeHub
Да: в стиле LobeHub это обычно означает разнести FastAPI-приложение по слоям API → Application/Use Cases → Domain → Infrastructure, чтобы endpoint был тонким, бизнес-правила жили вне FastAPI, а зависимости смотрели внутрь, а не наружу [89] [90] [91].
Что значит «в стиле LobeHub»
В описании skill fastapi-clean-architecture на LobeHub акцент сделан на четком разделении слоев, dependency injection, repository pattern и тестируемости, где API-слой отвечает за маршруты и валидацию, Domain — за сущности и правила, а Infrastructure — за БД и внешние адаптеры [89] [90].
Это хорошо сочетается с фаулеровским подходом: не переписывать всё в один заход, а постепенно выделять use cases, порты и адаптеры, сохраняя поведение системы [92] [93].
Типичный legacy FastAPI
Обычно legacy FastAPI-модуль выглядит так: route сам читает данные из SQLAlchemy, сам проверяет права, сам реализует бизнес-логику и сам вызывает внешние сервисы. Такой код трудно тестировать и он жестко связан с FastAPI и ORM [94] [91].
Упрощенный пример «до»:
@router.post (»/users/ {user_id} /deactivate»)
def deactivate_user (
user_id: int,
db: Session = Depends (get_db),
current_user: User = Depends (get_current_user),
):
user = db.query(UserModel).filter(UserModel.id == user_id).first ()
if not user:
raise HTTPException (404, «User not found»)
if current_user.role!= «admin»:
raise HTTPException (403, «Forbidden»)
if user.status == «inactive»:
raise HTTPException (400, «Already inactive»)
user.status = «inactive»
db.commit ()
send_email (user. email, «Your account was deactivated»)
return {«id»: user.id, «status»: user.status}
Здесь смешаны presentation, application logic, persistence и integration side effects, то есть почти все слои одновременно [94] [91].
Куда двигаться
Для clean architecture в стиле LobeHub целевая структура обычно такая:
Ключевой принцип: бизнес-правила не должны зависеть от FastAPI, а репозитории и адаптеры реализуют абстракции, определенные ближе к домену или application-слою [89] [91].
Пошаговый рефакторинг
Начинать лучше не с «перестроим весь проект», а с одного вертикального среза — одного endpoint и связанных с ним правил [92] [97].
Практически последовательность такая:
— Зафиксировать текущее поведение тестами.
— Вынести бизнес-операцию в use case.
— Вынести доступ к данным в repository interface + implementation.
— Изолировать внешние вызовы через порт.
— Оставить router только как адаптер HTTP ↔ use case [98] [89] [91].
Пример «после»
Domain
from dataclasses import dataclass
from typing import Protocol, Optional
@dataclass
class User:
id: int
email: str
status: str
class UserRepository (Protocol):
def get_by_id (self, user_id: int) -> Optional [User]:…
def save (self, user: User) -> None:…
class NotificationPort (Protocol):
def send_deactivated (self, email: str) -> None:…
В domain остаются сущность и абстракции, без FastAPI, SQLAlchemy и внешних SDK [89] [90].
Application
class UserNotFoundError (Exception): pass
class ForbiddenError (Exception): pass
class AlreadyInactiveError (Exception): pass
class DeactivateUserUseCase:
def __init__ (self, users: UserRepository, notifications: NotificationPort):
self.users = users
self.notifications = notifications
def execute (self, actor_role: str, user_id: int) -> User:
user = self.users.get_by_id (user_id)
if not user:
raise UserNotFoundError ()
if actor_role!= «admin»:
raise ForbiddenError ()
if user.status == «inactive»:
raise AlreadyInactiveError ()
user.status = «inactive»
self.users.save (user)
self.notifications.send_deactivated (user. email)
return user
Use case становится центром сценария: он ничего не знает о FastAPI, HTTPException и конкретной БД, зато выражает бизнес-правило явно [89] [95] [91].
Infrastructure
class SqlAlchemyUserRepository:
def __init__ (self, db: Session):
self. db = db
def get_by_id (self, user_id: int) -> Optional [User]:
row = self.db.query(UserModel).filter(UserModel.id == user_id).first ()
if not row:
return None
return User(id=row.id, email=row. email, status=row.status)
def save (self, user: User) -> None:
row = self.db.query(UserModel).filter(UserModel.id == user.id).first ()
row.status = user.status
self.db.commit ()
class EmailNotificationAdapter:
def send_deactivated (self, email: str) -> None:
send_email (email, «Your account was deactivated»)
Infrastructure реализует контракты и может быть заменена без изменения use case [89] [91].
API
def get_deactivate_user_use_case (
db: Session = Depends (get_db),
) -> DeactivateUserUseCase:
users = SqlAlchemyUserRepository (db)
notifications = EmailNotificationAdapter ()
return DeactivateUserUseCase (users, notifications)
@router.post (»/users/ {user_id} /deactivate»)
def deactivate_user (
user_id: int,
current_user: CurrentUser = Depends (get_current_user),
use_case: DeactivateUserUseCase = Depends (get_deactivate_user_use_case),
):
try:
user = use_case.execute(actor_role=current_user.role, user_id=user_id)
return {«id»: user.id, «status»: user.status}
except UserNotFoundError:
raise HTTPException (status_code=404, detail=«User not found»)
except ForbiddenError:
raise HTTPException (status_code=403, detail=«Forbidden»)
except AlreadyInactiveError:
raise HTTPException (status_code=400, detail=«Already inactive»)
Router теперь тонкий: он принимает HTTP-запрос, вызывает use case и отображает доменные ошибки в HTTP-ответы, что как раз и соответствует внешнему адаптеру в clean architecture [89] [91].
Промпты для такого рефакторинга
Вот хорошие промпты, если хочешь сделать именно такой переход:
— «Проанализируй этот FastAPI endpoint и разложи код по слоям: API, application/use case, domain, infrastructure. Не меняй поведение, только предложи первый безопасный шаг» [89] [91].
— «Вынеси бизнес-логику из route в use case. FastAPI и HTTPException должны остаться только в API-слое» [89] [90].
— «Создай repository interface в domain и SQLAlchemy implementation в infrastructure, сохрани поведение endpoint» [89] [91].
— «Изолируй email/Redis/external API через port/adapters pattern, чтобы use case можно было тестировать без инфраструктуры» [89] [93].
— «Покажи минимальный рефакторинг одного vertical slice, без полной перестройки проекта» [92] [97].
Почему это лучше
Такой стиль дает более чистые тесты, потому что use case можно проверять без FastAPI и без реальной БД, а инфраструктурные детали можно подменять fake-реализациями [89] [99].
Еще он уменьшает зависимость от фреймворка: FastAPI остается на внешнем краю системы, а бизнес-ядро становится переносимым и проще в сопровождении [91] [90].
Полезные источники
Для такого рефакторинга стоит смотреть:
— LobeHub fastapi-clean-architecture skill description [89] [90].
— ivan-borovets/fastapi-clean-example, где отдельно подчеркивается, что Depends не должен протекать во внутренние слои [91].
— Обзор clean architecture с use cases и repository pattern для FastAPI [95] [96].
— Fowler по маленьким шагам рефакторинга и seams для безопасной миграции legacy-кода [92] [93].
Если хочешь, я могу следующим сообщением сделать полный мини-проект-скелет FastAPI «legacy → clean architecture» с деревом папок и примерами файлов.
⁂
Перечисли паттерны и подробно описать, дать ссылки на литературу
Ниже — основные паттерны для рефакторинга FastAPI/legacy-кода в сторону Clean Architecture, в духе LobeHub и похожих шаблонов: сначала тонкий API-слой, затем use case/application, потом domain и infrastructure [104] [105] [106].
Паттерны слоёв
1. Thin Controller
Контроллер или FastAPI-route делает только прием запроса, валидацию входа, вызов use case и преобразование результата в HTTP-ответ. Логика предметной области в нем не живет [104] [105].
Это снижает связанность с фреймворком и делает endpoint проще для тестирования [104].
2. Use Case / Interactor
Бесплатный фрагмент закончился.
Купите книгу, чтобы продолжить чтение.