Один из наших проектов нужно было перенести на Kotlin, чтобы включить его в общую экосистему клиента и упростить поддержку. Мы должны были курировать процесс и следить за итоговой реализацией, в которой заказчик решил использовать ИИ.

В этой статье рассказываем о реальных возможностях нейронки для миграции бэкенда и про то, с чем лучше к ней не обращаться — чтобы не тратить ни время, ни нервы.

Главный челлендж переноса бэкенда на совершенно другой стек — сложность архитектуры и объем кодовой базы. Ты можешь хорошо разбираться в решениях на целевом коде, но при этом не знать то, на чем базируется старая система.

Так было и на нашем проекте. Миграцией занимался Senior клиента, который впервые столкнулся с исходным стеком, но понимал, что должно получиться в итоге. Для переноса небольшого сервиса с Node.js на Java-стек он использовал Claude.

Нейросеть обработала модуль целиком: сгенерировала контроллеры, DTO и базовые тесты. Разработчик быстро проверял результат, правил мелкие неточности и адаптировал код под нужные паттерны.

На все ушло примерно месяц. Мы вывели проект в продакшн, постепенно переключали ручки API и тестировали функционал. Еще около двух — заняла доработка и стабилизация. 

Для заказчика ИИ показал себя выгодным инструментом. С его помощью один Java-специалист пересоздал сервис в два раза быстрее, чем его делали 2 бэкенд-разработчика на Node.js. Большая часть генерации действительно сэкономила время.

В чем же нейросеть реально полезна

1. Конвертация типовых структур

Если нужно перевести десятки DTO, моделей или интерфейсов между языками — это идеальная зона для ИИ. Такие задачи однообразны, правила преобразования предсказуемы, а ошибки легко проверить.

Пример запроса:

Перепиши этот код на Kotlin с использованием Spring Boot. Сохрани сигнатуру метода и добавь типизацию.

export async function getUser(req, res) {
  const user = await db.findUser(req.params.id);
  res.json(user);
}

Пример результата:

@RestController
@RequestMapping("/user")
class UserController(private val userService: UserService) {
    @GetMapping("/{id}")
    fun getUser(@PathVariable id: Long): ResponseEntity<UserDto> {
        val user = userService.findUser(id)
        return ResponseEntity.ok(user)
    }
}

Код не финальный, но снимает большую часть рутинной работы.

2. Генерация тестов и документации

ИИ неплохо справляется с шаблонными юнит-тестами, когда структура кода однотипна. Он может покрыть базовые кейсы, а разработчик затем дополнит сложные сценарии.

Точно так же можно поручить нейросети генерацию документации по публичным методам API. Она формирует описания и примеры, а человек уточняет детали и форматирует под требования команды.

Пример запроса:

Составь описание API для этого контроллера в формате OpenAPI 3.0 и предложи набор тестов для проверки корректности ответов.

Полученные тексты не стоит копировать в чистом виде, но они помогут с ревью и структурой.

3. Анализ и рефакторинг кода

Миграцию редко выполняют те же специалисты, которые писали исходный код. Если документации нет, то ИИ может быстро разобраться в чужом проекте. Нейросеть опишет структуру модулей, укажет потенциальные зависимости или дублирование логики. 

Пример запроса:

Объясни, что делает этот модуль. Определи входные и выходные данные, внешние вызовы и зависимости.
// order-service.js

import { getUserById } from './user-service.js';
import { getProductsInCart } from './product-service.js';
import { OrderRepository } from './db/OrderRepository.js';

export async function createOrder(userId) {
  const user = await getUserById(userId);
  const products = await getProductsInCart(userId);

  const total = products.reduce((sum, item) => sum + item.price, 0);
  const order = await OrderRepository.save({
    userId: user.id,
    products,
    total,
    status: 'created'
  });
  return { id: order.id, total, status: order.status };
}

Пример результата:

Модуль order-service отвечает за создание заказа. Вызывает функции getUserById (модуль user-service) и getProductsInCart (модуль product-service). Использует OrderRepository для записи данных в базу. На вход получает userId, возвращает JSON с идентификатором заказа, суммой и статусом.

Такие описания можно брать за основу для будущей технической документации или маппинга между сервисами.

Пример запроса:

Проанализируй этот код. Найди неиспользуемые импорты, повторяющиеся функции и зависимости между файлами.

[Очень длинный код]

Результат — список модулей, от которых можно отказаться, и явное понимание, как устроен старый проект.

4. Работа с результатами SQL-запросов

ИИ может сгенерировать код для маппинга резалт сета в сущности при использовании jdbcTemplate.

Пример запроса:

Сгенерируй RowMapper для JdbcTemplate, который сопоставляет результат SQL-запроса с сущностью User (+ контекст: [запрос в БД] и [сущность]).

RowMapper<User> userRowMapper = (rs, rowNum) -> {
    User user = new User();
    user.setId(rs.getLong("id"));
    user.setEmail(rs.getString("email"));
    user.setCreatedAt(
        rs.getTimestamp("created_at").toLocalDateTime()
    );
    return user;
};

Это формализованная задача без бизнес-логики, где результат легко проверить и доработать вручную. 

Для переноса бэкенда удобнее использовать диалоговые чат-боты, которые умеют разбирать большие фрагменты кода и сохранять логику при конверсии. Это Claude, ChatGPT с Codex и IDE-ассистенты вроде Copilot или Codeium.

Чтобы не рисковать данными, лучше работать в закрытых окружениях или маскировать доменные имена и идентификаторы перед отправкой в чат.

С чем нейросеть не справится

После успешной реализации сервиса на очереди был более крупный модуль. Это уже полноценное веб-приложение с множеством интеграций по HTTP и Kafka, сложной структурой базы данных и фронтовыми взаимодействиями. Разработчик применил тот же подход, но результат оказался противоположным.

В итоговом коде накопилось множество конфликтов, тесты не проходили, а большая часть логики требовала ручной переписки. Все называли его «кодом Шредингера»: формально он есть, но не работает.

Причина оказалась в разнице масштабов. Небольшой сервис, где логика прозрачна и нет сложных зависимостей, хорошо подходит под генерацию. Крупное приложение — с интеграциями, кешами и ролевой моделью — требует архитектурного мышления. В таких случаях нейросеть перестает быть помощником и превращается в источник дополнительной нагрузки.

1. Ошибки в ролевой модели

В проекте была сложная система ролей. Права доступа зависели не только от фактической роли, но и от связей конкретного пользователя с сущностями. Иногда — с ограничениями по атрибутам. 

Нейросеть сгенерировала вариант, в котором часть логики была реализована через RBAC-библиотеку. Остальная логика, включая ABAC, была догенерирована отдельно и оказалась в сервисном слое.

В итоге получился спагетти-код в сервисном слое + отдельный файл под RBAC. Решение переделали.

2. Игнорирование контекста

Решая задачу с ABAC, ИИ сгенерировал Request Wrapper, выполняющий вычитку и кэширование request body. Он был установлен как фильтр на все POST/PUT запросы с наивысшим приоритетом, что сломало обработку multipart-запросов. Все пришлось переписать.

3. Проблемы при работе с конкретными фреймворком

В нашем случае — со Spring. Для авторизации сервис должен был один раз загрузить JWKS-ключ и кешировать его. Нейросеть поставила аннотацию @Cacheable на метод, но не учла, что он вызывается из того же класса — в таком случае кеш не работает.

В результате на каждый запрос происходил новый HTTP-вызов к серверу авторизации. Исправили просто — вызвали бин через контекст, чтобы кеширование заработало.

4. Ошибки в модели выполнения

При генерации кода нейросеть выдала полностью синхронную реализацию, где операции выполнялись строго последовательно, блокируя поток. Из-за этого сервис потерял в производительности. 

В целом же код ИИ «грязный». Нейросеть не знает проектного контекста, а рассказывать ей о нем не следует. Она нужна только для того, чтобы сократить время на рутину и помочь разобраться в новом для себя стеке. 

Типичные проблемы:
— неточности в типах данных — nullable, generics;
— потеря бизнес-логики при конвертации;
— дублирование кода;
— некорректные зависимости или циклы импортов.

Валидировать логику должны тимлид и QA. Обязательный этап — ручное ревью и рефакторинг.

При хорошо выстроенном пайплайне и подготовленных артефактах миграция становится проще, но остается зоной рисков. Многое идет не по плану: меняются требования, всплывают зависимости, ломаются связи между сервисами. 

Так или иначе, все узкие места связаны с архитектурой. Чем она сложнее, тем больше уязвимых мест для переноса и меньше участия ИИ. В этом контексте код невозможно переписать построчно, самому не разобравшись с логикой системы. 

А приходилось ли вам участвовать в миграции проекта на другой стек — с какого на какой? Использовали нейросеть?

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


  1. Dhwtj
    13.01.2026 13:29

    @RestController
    @RequestMapping("/user")
    class UserController(private val userService: UserService) {
        @GetMapping("/{id}")
        fun getUser(@PathVariable id: Long): ResponseEntity<UserDto> {
            val user = userService.findUser(id)
            return ResponseEntity.ok(user)
        }
    }

    Может да, а может и нет. JS молчит про типы. Может, id это int, string, uuid...

    Такому только дай волю он весь код сломает

    Модуль order-service отвечает за создание заказа. Вызывает функции getUserById (модуль user-service) и getProductsInCart (модуль product-service). Использует OrderRepository для записи данных в базу. На вход получает userId, возвращает JSON с идентификатором заказа, суммой и статусом.

    И не убирает ничего из корзины когда заказ сделан. Глюк